﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.Threading;
using System.Timers;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using System.Runtime.Remoting.Contexts;
using msdcp.Properties;
using System.Globalization;

namespace msdcp
{
    public partial class MainForm : Form
    {
        #region Constants
        //-------------------   CONSTANTS   --------------------------
        private const int timerPeriod = 31000;
        private const string defaultPortName = "-----";
        private const int MAX_DOORS = 3;
        private const int PAGE_SIZE = 263;
        private const int IF_TOTAL_PAGES = 2048;
        private const int EEPROM_PAGE_SIZE = 256;
        private const int EEPROM_FULL_SIZE = 1024;
        private const int FILTER_SIZE_BASE = 17;
        private const int FILTER_SIZE_182 = 20; //Количество байт, занятых под настройки фильтра
        private const int BN_STRING_LEN = 17;
        private const int EEPROM_TRIAD_INDEX_ADDRESS = 0x12;
        private const int EEPROM_TRIAD_ARRAY_ADDRESS = 0x14;
        private const int EEPROM_TOTAL_PAGES = EEPROM_FULL_SIZE / EEPROM_PAGE_SIZE;
        private const int EEPROM_TRIAD_SIZE = 9;
        private const int READ_TIMEOUT = 300;
        private const int WRITE_TIMEOUT = 300;
        private const int _SYS_STAT_NEED_CHECK_TRACKER_BIT = 12;
        private const int MAX_ATTEMPTS = 10;
        private const byte MAX_PASS_THRU_CLOSED = 3;
        //------------------------------------------------------------ 
        #endregion

        private SerialPort comPort;
        private string currentPortName;
        private List<byte> response;
        private List<byte> respClone;
        private List<string> portsWithMSD;
        private bool needToViewHex = false;
        private bool needToViewText = true;
        private System.Timers.Timer timer;
        private string journalPath;
        private string journalName;
        private int filterDataSize;
        private int numOfRecords;           // Number of records in a system journal.
        private int numOfTrans;             // Number of transfers has been made.
        private IntF_Service_Area_t ifsa;   // Internal flash service area.
        private ExtF_Service_Area_t efsa;   // External flash service area.
        private bool continueTransfer;      // Flag that shows if the current transfer is to be continued.

        private AutoResetEvent are_ResponseProcessed;
        private ManualResetEvent mre_CommandSent;
        private EventWaitHandle ewh_OperationCompleted;
        private SystemState sys;
        Label[] triadDateLabel;
        Label[] triadTimeLabel;

        int Page_Size_L;//Размер страницы;

        #region Delegates and events
        /////////////////////////////////////////////////////////////////////////////
        //
        // Delegates and events.
        //
        private delegate void SelectPortHandler();
        private delegate void UpdateViewStateHandler(List<byte> responseClone);
        private delegate void UpdateStatusBarHandler();
        private UpdateStatusBarHandler OnUpdateStatusBar;
        private event UpdateViewStateHandler OnUpdateViewState;
        private delegate void ReadFilterSetHandler();
        private ReadFilterSetHandler OnReadFilterSet;
        private delegate void UpdateFilterSetHandler();
        private UpdateFilterSetHandler OnUpdateFilterSet;
        private delegate void SaveEEPROMHandler();
        private SaveEEPROMHandler OnSaveEEPROM;
        private delegate void FillGridHandler();
        private FillGridHandler OnFillGrid;
        private delegate void ProgressChangeHandle(int percent);
        private ProgressChangeHandle OnProgressChangeHandle;
        private delegate void HideProgressBarHandle();
        private HideProgressBarHandle OnHideProgressBar;
        private delegate void UserWorkDone(object result);
        private UserWorkDone OnUserWorkDone;
        delegate void onClearDataGrid();
        delegate void onUpdatePortMonitor(List<byte> bL);
        //
        ////////////////////////////////////////////////////////////////////////// 
        #endregion

        private DateTime baseDate = new DateTime(2007, 1, 1);
        private List<byte> loadedSettings;
        private List<byte> uploadingSettings;
        private IntF_Filter_Area_t_182 filter_data;
        private const int UNKNOWN_COEFF = 20;
        private BackgroundWorker worker;
        private ResponseStatus responseStatus = ResponseStatus.Wait;
        private ushort pageToRead = 0;
        private ushort pageToWrite = 0;
        private List<byte> pageContent;
        private object locker;
        private List<byte> EEPROM;
        private string defaultBusNumber = "-- --- --- ---";
        private MsdVersion msdVersion;
        private string EEPROM_Directory;
        private string timePattern = "HH:mm";
        private string longTimePattern = "HH:mm:ss.fff";
        private int cellsPerRow = 16;
        private ushort currIndex;           // Current triad index.
        private ushort currTriadAddress;    // Current triad address.
        private DataGridViewCellStyle highlightedCellStyle;
        private bool isOldVersion;
        private bool cancel;
        private TriadState[] triadStates;
        private string busNumber;
        private ushort totalPagesToWrite; // The number of pages to write to MSD.
        private System_Status_1 System_Status_1;
        private _Sys_descriptor_t g_Main_Descriptor;
        private SystemErrors Error_flags;
        private StringBuilder sbSystemData;


        private string[] statusLabel = 
        {
            "Не готов", 
            "Готов"
        };
        
        
        


        public MainForm()
        {
            InitializeComponent();
            
        }

        private void SetWindowProperties()
        {
            List<string> section = SettingsProvider.GetSection("[WindowState]");
            if (section.Count > 0)
            {
                this.WindowState = (FormWindowState)(int.Parse(section[0]));
            }


            CultureInfo cultInf = (CultureInfo)Thread.CurrentThread.CurrentCulture.Clone();
            cultInf.NumberFormat.NumberDecimalSeparator = ",";
            Thread.CurrentThread.CurrentCulture = cultInf;

            section = SettingsProvider.GetSection("[WindowDimensions]");
            if (section.Count > 1)
            {
                string strWidth = section[0].Split('=').Last();
                string strHeight = section[1].Split('=').Last();
                this.Width = int.Parse(strWidth);
                this.Height = int.Parse(strHeight);
                this.portMonitor.Columns[0].FillWeight = float.Parse(section[2].Split('=').Last(), cultInf) == 0 ? 100 :
                   float.Parse(section[2].Split('=').Last(), cultInf);
                this.portMonitor.Columns[1].FillWeight = float.Parse(section[3].Split('=').Last(), cultInf) == 0 ? 100 :
                   float.Parse(section[3].Split('=').Last(), cultInf);
                this.portMonitor.Columns[2].FillWeight = float.Parse(section[4].Split('=').Last(), cultInf) == 0 ? 100 :
                   float.Parse(section[4].Split('=').Last(), cultInf);
                this.portMonitor.Columns[3].FillWeight = float.Parse(section[5].Split('=').Last(), cultInf) == 0 ? 100 :
                   float.Parse(section[5].Split('=').Last(), cultInf);
            }
            section = SettingsProvider.GetSection("[WindowPosition]");
            if (section.Count > 1)
            {
                string strX = section[0].Split('=').Last();
                string strY = section[1].Split('=').Last();
                this.Location = new Point(int.Parse(strX), int.Parse(strY));
            }
        }

        private void WriteFilterSettings(object args)
        {
            try
            {
                // Останавливаем таймер, чтобы он не помешал приему.
                timer.Stop();

                // Считываем текущие значения контролов.
                this.Invoke(OnReadFilterSet);
                // Сохраняем текущие значения фильтров, т.к. после получения
                // 0-й страницы они автоматически будут перезаписаны значениями из МСД.
                IntF_Filter_Area_t_182 fd = new IntF_Filter_Area_t_182(filter_data);

                if (comPort.IsOpen)
                {
                    while (true)
                    {
                        pageToWrite = 0;
                        pageToRead = 0;
                        pageContent.Clear();
                        SendCommand(Command.PAGE_READ, pageToRead);
                        are_ResponseProcessed.WaitOne(1000);

                        if (pageContent.Count == Page_Size_L)
                        {
                            // Внедряем текущие значения настроек в полученную страницу.
                            List<byte> pc = new List<byte>();
                            // Copying values from pageContent to pc (in order to keep pageContent safe).
                            foreach (byte b in pageContent)
                            {
                                pc.Add(b);
                            }

                            // Exchanging bytes that represent filter_data structure.
                            pc.RemoveRange(
                                Page_Size_L -
                                (filterDataSize+1),
                                (filterDataSize+1));
                            List<byte> newSet = FilterDataToList(fd);
                            pc.InsertRange(pc.Count, newSet);

                            // Overwrite pageContent with new values.
                            pageContent.Clear();
                            foreach (byte b in pc)
                            {
                                pageContent.Add(b);
                            }

                            //----Пауза, необходимая для считывания информации----//
                            Thread.Sleep(1000);
                            
                            // Восстанавливаем в filter_data текущие значения.
                            filter_data = new IntF_Filter_Area_t_182(fd);
                            // Обновляем значения контролов.
                            this.Invoke(OnUpdateFilterSet);
                            SendCommand(Command.PAGE_WRITE, pageToWrite);
                            are_ResponseProcessed.WaitOne(1000);
                            break;
                        } // if (pageContent.Count == PAGE_SIZE)

                    }// while(true)

                }
            }
            catch (ArgumentException ae)
            {
                ErrorReport err = new ErrorReport(ae.Message + ae.StackTrace);
                err.LogName = "msdcpErrorReport.log";
                err.AddToLog();
                //Debugger.Break();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + ex.StackTrace);
            }
            finally
            {
                // Возобновляем таймер.
                timer.Start();
            }
        }


        private void EnumPortsWithMSD()
        {
            portsWithMSD.Clear();
            string[] ports = SerialPort.GetPortNames();
            foreach (string portName in ports)
            {
                // Check if user cancelled this operation.
                if (cancel)
                {
                    break;
                }
                // Try to find MSD in a particular port.
                if (FindMSD(portName))
                {
                    portsWithMSD.Add(portName);
                    break;
                }
            }
        }

        // Ищет МСД в указанном порту.
        // Самостоятельно открывает
        // и закрывает порт.
        private bool FindMSD(string portName)
        {
            bool result = false;

            //////////////////////////////////////////////////////////////////////////////////////////
            sys = SystemState.NOT_READY;
            response.Clear();
            //////////////////////////////////////////////////////////////////////////////////////////
            try
            {
                comPort.PortName = portName;
                ConfigurePort();

                comPort.Open();
                if (comPort.IsOpen)
                {
                    SendCommand(Command.START_SESSION, null);
                    are_ResponseProcessed.WaitOne(500);

                    // Если получили статус "READY", то нашли МСД.
                    if (responseStatus == ResponseStatus.Accepted &&
                        sys == SystemState.READY)
                    {
                        result = true;
                    }
                }
            }
            catch (InvalidOperationException)
            {
                //MessageBox.Show(ioe.Message + ioe.StackTrace);
                //ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }
            catch (ArgumentOutOfRangeException)
            {
                //MessageBox.Show(aoore.Message + aoore.StackTrace);
                //ErrorReport err = new ErrorReport(aoore.Message + aoore.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }
            catch (ArgumentException)
            {
                //MessageBox.Show(ae.Message + ae.StackTrace);
                //ErrorReport err = new ErrorReport(ae.Message + ae.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }
            catch (System.IO.IOException)
            {
                //MessageBox.Show(ioe.Message + ioe.StackTrace);
                //ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }
            catch (UnauthorizedAccessException)
            {
                //MessageBox.Show(uae.Message + uae.StackTrace);
                //ErrorReport err = new ErrorReport(uae.Message + uae.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }
            finally
            {
                try
                {
                    if (comPort.IsOpen)
                    {
                        comPort.Close();
                    }
                }
                catch (System.IO.IOException ioe)
                {
                    ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                    err.LogName = "msdcpErrorReport.log";
                    err.AddToLog();
                    MessageBox.Show(ioe.Message);
                }
                catch (InvalidOperationException ioe)
                {
                    MessageBox.Show(ioe.Message + ioe.StackTrace);
                    //ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                    //err.LogName = "msdcpErrorReport.log";
                    //err.AddToLog();
                    //Debugger.Break();
                }
            }

            return result;
        }


        private void MainForm_OnUpdateViewState(List<byte> responseClone)
        {
            UpdateViewState(responseClone);
        }

        private void UpdateViewState(List<byte> responseClone)
        {
            UpdatePortMonitor(responseClone);
            UpdateStatusBar();
        }

        private void ShowProgressBar()
        {
            if (progressBar != null)
            {
                progressBar.Value = 0;
                progressBar.Visible = true;
            }
        }

        private void HideProgressBar()
        {
            try
            {
                if (progressBar != null)
                {
                    progressBar.Visible = false;
                }
            }
            catch { }
        }

        private void worker_SearchForMSDCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                //////////////////////////////////////////////////////////
                //
                //                  ОСНОВНОЙ ПОТОК
                FreeWorker();
                HideProgressBar();

                numOfTrans = 0;
                numOfRecords = 0;

                // Если порт обнаружен и получен ответ от МСД.
                if (!string.IsNullOrEmpty(currentPortName) && currentPortName != defaultPortName)
                {
                    comPort.PortName = currentPortName;
                    UpdateConfig();
                    ConfigurePort();

                    // Fixing Microsoft bug with SerialPort class.
                    SerialPortFixer.Execute(currentPortName);

                    comPort.Open();
                    GC.SuppressFinalize(comPort.BaseStream);
                    // Читаем EEPROM и заполняем статус-бар.
                    worker = new BackgroundWorker();
                    worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
                    worker.DoWork += new DoWorkEventHandler(ReadEEPROM);
                    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ReadingEepromCompleted);
                    worker.WorkerReportsProgress = true;
                    worker.WorkerSupportsCancellation = true;

                    progressBar.Style = ProgressBarStyle.Blocks;
                    ShowProgressBar();

                    triadStates[0] = TriadState.Normal;
                    triadStates[1] = TriadState.Normal;
                    triadStates[2] = TriadState.Normal;
                    for (int i = 0; i < 3; i++)
                    {
                        HighlightTriadLabel(i);
                    }

                    worker.RunWorkerAsync();
                }
                else // Если МСД не обнаружен.
                {
                    // Повторяем поиск.
                    List<string> section = new List<string>();
                    section.Add(defaultPortName);
                    SettingsProvider.UpdateSection("[LastUsedComPort]", section);
                    currentPortName = defaultPortName;

                    busNumber = defaultBusNumber;
                    msdVersion = new MsdVersion(0);
                }
            }
            catch (System.IO.IOException) // Если порт не обнаружен.
            {
                // Повторяем поиск.
                List<string> section = new List<string>();
                section.Add(defaultPortName);
                SettingsProvider.UpdateSection("[LastUsedComPort]", section);
                currentPortName = defaultPortName;
                busNumber = defaultBusNumber;
                msdVersion = new MsdVersion(0);
            }

            cancel = false;
            toolButtonCancel.Enabled = false;
            UpdateStatusBar();
        }

        private void FreeWorker()
        {
            if (worker != null)
            {
                if (worker.IsBusy)
                {
                    worker.CancelAsync();
                }
                worker.Dispose();
                worker = null;
            }
        }

        private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                if (!((BackgroundWorker)sender).CancellationPending)
                {
                    progressBar.Value = e.ProgressPercentage;
                }
            }
            catch (Exception ex)
            {
                ErrorReport err = new ErrorReport(ex.Message + ex.StackTrace);
                err.LogName = Program.LOG_NAME;
                err.AddToLog();
            }
        }


        private void worker_SearchForMSD(object sender, DoWorkEventArgs e)
        {
            // Сначала ищем в порту, сохраненном в настройках.
            List<string> section = SettingsProvider.GetSection("[LastUsedComPort]");
            string lastUsedPort = string.Empty;
            if (section.Count > 0)
            {
                lastUsedPort = section[0];
                if (FindMSD(lastUsedPort))
                {
                    currentPortName = lastUsedPort;
                }
                else // Если в сохраненном в конфиге порту МСД не обнаружен.
                {
                    EnumPortsWithMSD();
                    if (portsWithMSD.Count > 0)
                    {
                        currentPortName = portsWithMSD[0];
                    }
                    else // Если не найдено ни одного порта с МСД.
                    {
                        responseStatus = ResponseStatus.Wait;
                    }
                }
            }
            else // Если секция конфига не прочитана.
            {
                //MessageBox.Show("Файл конфигурации поврежден");
            }
        }


        private void MainForm_OnSelectPort()
        {
            selectPortMenuItem_Click(this, new EventArgs());
        }

        private void SetResponseStatus(object args)
        {
            List<byte> buffer = (List<byte>)args;

            if (buffer.Count > 3)
            {
                // Проверяем соответствие идентификатора.
                if (buffer[Program.SENDER_BYTE] == (byte)Unit.ID_MSD)
                {
                    byte cmd = buffer[Program.COMMAND_BYTE];
                    // Проверяем, присутствет ли в ответе флаг COMMAND_TRUE
                    if ((cmd & (byte)Command.COMMAND_TRUE) != 0 ||
                        (cmd & (byte)Command.COMMAND_FALSE) != 0)
                    {

                        // Команда обработана успешно.
                        ushort resLen = (ushort)((buffer[Program.LEN_MSB] << 8) | buffer[Program.LEN_LSB]);

                        // Проверяем, соответствует ли длина ответа ожидаемой.
                        if (buffer.Count == resLen)
                        {
                            // Проверяем контрольную сумму.
                            byte cs = 0;
                            cs = ChecksumCalc(buffer.GetRange(0, buffer.Count - 1));

                            // Если контрольная сумма совпала.
                            if (cs == buffer.Last())
                            {
                                responseStatus = ResponseStatus.Accepted;
                                sys = SystemState.READY;
                            }
                            else // Если контрольная сумма не совпала.
                            {
                                responseStatus = ResponseStatus.ChecksumError;
                            }
                        }
                        else // Если фактическая длина ответа не соответствует длине, 
                        // объявленной в теле ответа (получили не все байты).
                        {
                            responseStatus = ResponseStatus.BrokenPackage;
                        }

                    }
                    else
                    {
                        // if we haven't received COMMAND_TRUE in command byte.
                        responseStatus = ResponseStatus.Denied;
                    }
                }
                else // Если первый байт - не идентификатор МСД.
                {
                    responseStatus = ResponseStatus.Ignored;
                }
            }
            else // Если длина ответа менее 4 байтов.
            {
                responseStatus = ResponseStatus.BrokenPackage;
            }

            ProcessResponse(buffer);
        }

        private void ProcessResponse(List<byte> responseClone)
        {
            try
            {
                if (responseStatus == ResponseStatus.Accepted)
                {
                    switch ((Command)responseClone[Program.COMMAND_BYTE])
                    {
                        case Command.START_SESSION | Command.COMMAND_TRUE:
                            break;
                        case Command.END_SESSION | Command.COMMAND_TRUE:
                            break;
                        case Command.PC_ONLINE | Command.COMMAND_TRUE:
                            break;
                        case Command.SET_DEBUG_MODE | Command.COMMAND_TRUE:
                            break;
                        case Command.CLEAR_DEBUG_MODE | Command.COMMAND_TRUE:
                            break;
                        case Command.EEPROM_READ | Command.COMMAND_TRUE:
                            byte pageNumber = responseClone[Program.DATA_START_BYTE];
                            int pos = pageNumber * EEPROM_PAGE_SIZE;
                            if (EEPROM.Count > pos)
                            {
                                // Выполняем вставку.
                                EEPROM.RemoveRange(pos, EEPROM_PAGE_SIZE);
                                EEPROM.InsertRange(pos, responseClone.GetRange(Program.DATA_START_BYTE + 1, EEPROM_PAGE_SIZE));
                            }
                            else
                            {
                                // Добавляем в конец.
                                EEPROM.AddRange(responseClone.GetRange(Program.DATA_START_BYTE + 1, EEPROM_PAGE_SIZE));
                            }
                            break;
                        case Command.SYNC_TIME | Command.COMMAND_TRUE:
                            break;
                        case Command.PAGE_READ | Command.COMMAND_TRUE:
                            List<byte> pc = new List<byte>();
                            pc = responseClone.GetRange(Program.DATA_START_BYTE, Page_Size_L);
                            // Если запрашивали 0 страницу, то нужны настройки.
                            if (pageToRead == 0)
                            {
                                loadedSettings.Clear();
                                loadedSettings.AddRange(pc.GetRange(
                                    Page_Size_L - // Размер страницы памяти МСД + длина запроса.
                                    (filterDataSize+1), // Размер структуры IntF_Filter_Area_t.
                                    (filterDataSize+1)));
                                FillFilterData();
                            }
                            else // Если запрашивали любую страницу, кроме 0.
                            {

                            }
                            lock (locker)
                            {
                                pageContent = pc.ToList();
                            }
                            break;
                        case Command.PAGE_WRITE | Command.COMMAND_TRUE:
                            // Если запрашивали 0 страницу, то записывали настройки.
                            if (pageToWrite == 0)
                            {
                                // Assuming that settings have been accepted.
                                msdVersion.Doors = (int)filter_data.doors;
                            }
                            else
                            {

                            }
                            break;
                        case Command.READ_BUS_NUMBER | Command.COMMAND_TRUE:
                            if (EEPROM.Count == EEPROM_FULL_SIZE)
                            {
                                EEPROM.RemoveRange(0, BN_STRING_LEN);
                                EEPROM.InsertRange(0, responseClone.GetRange(Program.DATA_START_BYTE, BN_STRING_LEN));
                            }

                            Encoding enc = Encoding.ASCII;
                            busNumber = enc.GetString((EEPROM.GetRange(0, BN_STRING_LEN)).ToArray());
                            busNumber = busNumber.TrimStart(' ', '\0');
                            busNumber = busNumber.TrimEnd(' ', '\0');
                            break;
                        case Command.WRITE_BUS_NUMBER | Command.COMMAND_TRUE:
                            break;
                        case Command.RS485_SWITCH | Command.COMMAND_TRUE:
                            break;
                        case Command.ERASE_INT_FLASH | Command.COMMAND_TRUE:
                            break;
                        case Command.SEND_CONFIG | Command.COMMAND_TRUE:
                            CreateExtFServiceArea(responseClone.ToArray());
                            break;
                        case Command.SEND_INTERVAL_DATA | Command.COMMAND_TRUE:
                            continueTransfer = false;
                            pageContent = responseClone.ToList();
                            break;
                        case Command.SEND_INTERVAL_DATA | Command.COMMAND_FALSE:
                            pageContent.Clear();
                            continueTransfer = false;
                            break;
                        case Command.SEND_INTERVAL_DATA | Command.COMMAND_TRUE | Command.COMMAND_FALSE:
                            continueTransfer = true;
                            pageContent = responseClone.ToList();
                            break;
                        case Command.UPGRADE | Command.COMMAND_TRUE:
                            break;
                        case Command.UPGRADE_BLOCK | Command.COMMAND_TRUE:
                            break;
                        case Command.UPGRADE_LAST_BLOCK | Command.COMMAND_TRUE:
                            break;
                    } // switch
                } // if (Accepted)

                if (OnUpdateViewState != null)
                {
                    IAsyncResult asyncRes = this.BeginInvoke(OnUpdateViewState, responseClone);
                    asyncRes.AsyncWaitHandle.WaitOne(300);
                }
            }
            finally
            {
                // The unit response has been processed, can continue.
                are_ResponseProcessed.Set();
            }
        }

        private void UpdatePortMonitor(List<byte> responseClone)
        {
            string newItemHex = string.Empty;
            string newItemText = string.Empty;
            try
            {
                if (needToViewHex)
                {
                    foreach (byte b in responseClone)
                    {
                        newItemHex += string.Format("{0:X2} ", b);
                    }
                }
                if (needToViewText)
                {
                    newItemText = ParseResponse(responseClone);
                }
                if (!string.IsNullOrEmpty(newItemText))
                {
                    int newRowIndex = portMonitor.Rows.Add(new string[] { string.Format("{0:000000}", 
                        (portMonitor.Rows.Count + 1)), 
                        DateTime.Now.ToString(longTimePattern), 
                        newItemText, newItemHex });

                    string nextJournalEntry = string.Format("{0, -17}{1, -8}{2, -18}{3, -60}{4}",
                        busNumber,
                        portMonitor.Rows[newRowIndex].Cells[0].Value.ToString(),
                        portMonitor.Rows[newRowIndex].Cells[1].Value.ToString(),
                        portMonitor.Rows[newRowIndex].Cells[2].Value.ToString(),
                        portMonitor.Rows[newRowIndex].Cells[3].Value.ToString());
                    ThreadPool.QueueUserWorkItem(new WaitCallback(AddNewJournalEntry), nextJournalEntry);

                    portMonitor.FirstDisplayedScrollingRowIndex =
                        portMonitor.Rows.Count - portMonitor.DisplayedRowCount(false);
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                //ErrorReport err = new ErrorReport(aoore.Message + aoore.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }


        private void AddNewJournalEntry(object args)
        {
            string entry = (string)args;
            StreamWriter sw = null;
            try
            {
                sw = new StreamWriter(Path.Combine(journalPath, journalName), true);
                sw.WriteLine(entry);
            }
            catch (IOException)// if the file is used by another process.
            {

            }
            catch (UnauthorizedAccessException uae)// if the current user does not have required permission to write
            {
                MessageBox.Show(uae.Message + uae.StackTrace);
            }
            finally
            {
                if (sw != null)
                {
                    sw.Close();
                }
            }
        }


        private string ParseResponse(List<byte> responseClone)
        {
            string newItemText = string.Empty;

            try
            {
                if (responseStatus == ResponseStatus.Accepted)
                {
                    Command com = (Command)(responseClone[Program.COMMAND_BYTE]);

                    switch (com)
                    {
                        case Command.START_SESSION | Command.COMMAND_TRUE:
                            newItemText = "МСД на связи"; ;
                            break;
                        case Command.END_SESSION | Command.COMMAND_TRUE:
                            newItemText = "Окончание сеанса";
                            break;
                        case Command.PC_ONLINE | Command.COMMAND_TRUE:
                            newItemText = "МСД на связи";
                            break;
                        case Command.SET_DEBUG_MODE | Command.COMMAND_TRUE:
                            newItemText = "Диагностический режим включен";
                            break;
                        case Command.CLEAR_DEBUG_MODE | Command.COMMAND_TRUE:
                            newItemText = "Диагностический режим отключен";
                            break;
                        case Command.EEPROM_READ | Command.COMMAND_TRUE:
                            newItemText = "Чтение содержимого EEPROM из МСД";
                            break;
                        case Command.SYNC_TIME | Command.COMMAND_TRUE:
                            newItemText = "Время МСД синхронизировано с ПК";
                            break;
                        case Command.PAGE_READ | Command.COMMAND_TRUE:

                            // Если запрашивали 0 страницу, то нужны настройки.
                            if (pageToRead == 0)
                            {
                                newItemText = "Параметры переданы из МСД в ПК";
                                UpdateFilterSetTools();
                                CreateServiceArea(responseClone.ToArray());
                            }
                            else
                            {
                                newItemText = string.Format("Страница {0} прочитана и передана", pageToRead);
                            }
                            break;
                        case Command.PAGE_WRITE | Command.COMMAND_TRUE:
                            if (pageToWrite == 0)
                            {
                                newItemText = "МСД сохранил параметры, полученные от ПК";
                            }
                            else
                            {
                                newItemText = string.Format("Страница {0} получена и сохранена", pageToWrite);
                            }
                            if (pageToWrite == IF_TOTAL_PAGES - 1)
                            {
                                newItemText = "Описатель загрузчика установлен";
                            }
                            break;
                        case Command.EEPROM_WRITE | Command.COMMAND_TRUE:
                            newItemText = "Запись в EEPROM МСД";
                            break;
                        case Command.READ_BUS_NUMBER | Command.COMMAND_TRUE:
                            newItemText = "Номер автобуса прочитан из МСД";
                            FillEEPROMGrid();
                            break;
                        case Command.WRITE_BUS_NUMBER | Command.COMMAND_TRUE:
                            newItemText = "Номер автобуса записан в МСД";
                            FillEEPROMGrid();
                            break;
                        case Command.RS485_SWITCH | Command.COMMAND_TRUE:
                            if (responseClone[Program.DATA_START_BYTE] == 1)
                            {
                                newItemText = "Контроль связи с трэкером включен";
                                trackerControlMenuItem.Checked = true;
                                msdVersion.Edition = 1;
                                versionStatusLabel.Text = msdVersion.ToString();
                            }
                            else
                            {
                                newItemText = "Контроль связи с трэкером выключен";
                                trackerControlMenuItem.Checked = false;
                                msdVersion.Edition = 0;
                                versionStatusLabel.Text = msdVersion.ToString();
                            }
                            break;
                        case Command.ERASE_INT_FLASH | Command.COMMAND_TRUE:
                            newItemText = "Внутренняя флэш МСД очищена";
                            break;
                        case Command.SEND_CONFIG | Command.COMMAND_TRUE:
                            newItemText = "Конфигурация получена";
                            break;
                        case Command.SEND_INTERVAL_DATA | Command.COMMAND_TRUE:
                            newItemText = string.Format("Пакет {0} из {1} получен от МСД", pageToRead + 1,
                                ((responseClone[Program.DATA_START_BYTE + 1] << 8) | responseClone[Program.DATA_START_BYTE]));
                            break;
                        case Command.SEND_INTERVAL_DATA | Command.COMMAND_TRUE | Command.COMMAND_FALSE:
                            newItemText = string.Format("Пакет {0} из {1} получен от МСД", pageToRead + 1,
                                ((responseClone[Program.DATA_START_BYTE + 1] << 8) | responseClone[Program.DATA_START_BYTE]));
                            break;
                        case Command.SEND_INTERVAL_DATA | Command.COMMAND_FALSE:
                            newItemText = "Передача системного журнала завершена ";
                            switch (responseClone[Program.DATA_START_BYTE])
                            {
                                case 0:
                                    newItemText += "без ошибок";
                                    break;
                                case 1:
                                    newItemText += "с ошибкой: ошибка доступа к внутренней флэш";
                                    break;
                                case 2:
                                    newItemText += "с ошибкой: ошибка при передаче данных";
                                    break;
                                case 3:
                                    newItemText += "с ошибкой: истек таймаут ожидания ответа от ПК";
                                    break;
                                case 4:
                                    newItemText += "с ошибкой: ошибка в ответе ПК";
                                    break;
                                case 5:
                                    newItemText += "с ошибкой: ошибка контрольной суммы";
                                    break;
                                default:
                                    newItemText += "с ошибкой: неизвестная ошибка";
                                    break;
                            }
                            break;
                        case Command.UPGRADE | Command.COMMAND_TRUE:
                            newItemText = "Начало выгрузки обновлений в МСД";
                            break;
                        case Command.UPGRADE_BLOCK | Command.COMMAND_TRUE:
                            newItemText = string.Format("Пакет {0} из {1} принят", pageToWrite + 1, totalPagesToWrite);
                            break;
                        case Command.UPGRADE_LAST_BLOCK | Command.COMMAND_TRUE:
                            newItemText = "Выгрузка обновлений в МСД завершена";
                            break;
                    } // switch
                } // if (Accepted)
                else // Если статус посылки не "Ответ принят" (Accepted)
                {
                    // Check if user cancelled a long-term operation.
                    if (!cancel)
                    {
                        Encoding enc = Encoding.Default;
                        newItemText += enc.GetString(responseClone.ToArray()).Replace(";", "\n");
                  
                        //newItemText += enc.GetString(mod.ToArray());
                        //foreach (byte b in responseClone)
                        //{
                        //    if (b == 0)
                        //    {
                        //        newItemText += char.ToString('.');
                        //    }
                        //    else if (b < 32)
                        //    {
                        //        newItemText += char.ToString((char)b);
                        //    }
                        //    else
                        //    {
                        //        byte[] arr = new byte[] { b };
                        //        if (enc != null)
                        //        {
                        //            newItemText += enc.GetString(arr);
                        //        }
                        //    }
                        //} // foreach ()
                    } // if (!cancel)
                } // else (if (Accepted))
            }
            catch (ArgumentOutOfRangeException)
            {
                //ErrorReport err = new ErrorReport(aoore.Message + aoore.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }
            catch (Exception)
            {
                //ErrorReport err = new ErrorReport(ex.Message + ex.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }

            return newItemText;
        }


        private void InitializeFilterData()
        {
            filter_data.doors = (byte)spinNumberOfDoors.Value;

            filter_data.min_in_pass_time = new byte[MAX_DOORS];
            for (int i = 0; i < MAX_DOORS; i++)
            {
                filter_data.min_in_pass_time[i] = (byte)(trackBarMinInPassTime.Value / UNKNOWN_COEFF);
            }

            filter_data.min_out_pass_time = new byte[MAX_DOORS];
            for (int i = 0; i < MAX_DOORS; i++)
            {
                filter_data.min_out_pass_time[i] = (byte)(trackBarMinOutPassTime.Value / UNKNOWN_COEFF);
            }

            filter_data.change_dir_time = new byte[MAX_DOORS];
            for (int i = 0; i < MAX_DOORS; i++)
            {
                filter_data.change_dir_time[i] = (byte)(trackBarReverseTime.Value / UNKNOWN_COEFF);
            }

            filter_data.shirt_sts0_time = new byte[MAX_DOORS];
            for (int i = 0; i < MAX_DOORS; i++)
            {
                filter_data.shirt_sts0_time[i] = (byte)(trackBarState0.Value / UNKNOWN_COEFF);
            }

            filter_data.filter_flags = new byte[MAX_DOORS];

            byte flags = 0;
            flags |= chkMinInPassTime.Checked ? (byte)0x01 : (byte)0x00;
            flags |= chkMinOutPassTime.Checked ? (byte)0x02 : (byte)0x00;
            flags |= chkReverseTime.Checked ? (byte)0x04 : (byte)0x00;
            flags |= chkState0.Checked ? (byte)0x08 : (byte)0x00;

            for (int i = 0; i < MAX_DOORS; i++)
            {
                filter_data.filter_flags[i] = flags;
            }

            filter_data.max_pass_thru_closed = new byte[MAX_DOORS];
            for (int i = 0; i < MAX_DOORS; i++)
            {
                filter_data.max_pass_thru_closed[i] = MAX_PASS_THRU_CLOSED;
            }

            filter_data.falps = SetFlapsVector();

            filter_data.Initialized = 0xcc;
        }

        /// <summary>
        ///Считывание данных фильтра 
        /// </summary>
        private void ReadFilterSetTools()
        {
            // Считываем текущие значения контролов.
            byte numOfDoors = (byte)spinNumberOfDoors.Value;

            int selectedDoor = (radioFront.Checked) ? 0 : (radioRare.Checked) ? 1 : 2;

            byte flags = 0;
            flags |= chkMinInPassTime.Checked ? (byte)(0x01) : (byte)0;
            flags |= chkMinOutPassTime.Checked ? (byte)(0x02) : (byte)0;
            flags |= chkReverseTime.Checked ? (byte)(0x04) : (byte)0;
            flags |= chkState0.Checked ? (byte)(0x08) : (byte)0;

            byte min_in_pass_time = (byte)(trackBarMinInPassTime.Value / UNKNOWN_COEFF);
            byte min_out_pass_time = (byte)(trackBarMinOutPassTime.Value / UNKNOWN_COEFF);
            byte change_dir_time = (byte)(trackBarReverseTime.Value / UNKNOWN_COEFF);
            byte shirt_sts0_time = (byte)(trackBarState0.Value / UNKNOWN_COEFF);
            byte max_pass_thru_closed = (byte)(trackBarMaxPass.Value);

            filter_data.falps = SetFlapsVector();
            filter_data.stsDiffTime = (byte)trackBarStsDiffTime.Value;

            // Если выставлен флаг "Применить ко всем дверям", то считываем установки для
            // текущей двери и сохраняем их для всех дверей.
            if (chkApplyToAll.Checked)
            {
                for (int i = 0; i < MAX_DOORS; i++)
                {
                    filter_data.min_in_pass_time[i] = min_in_pass_time;
                    filter_data.min_out_pass_time[i] = min_out_pass_time;
                    filter_data.change_dir_time[i] = change_dir_time;
                    filter_data.shirt_sts0_time[i] = shirt_sts0_time;
                    filter_data.filter_flags[i] = flags;
                    filter_data.max_pass_thru_closed[i] = max_pass_thru_closed;
                }
            }
            else
            {
                // Если не установлен флаг "Применять ко всем", то 
                // сохраняем изменения только для текущей двери.
                filter_data.min_in_pass_time[selectedDoor] = min_in_pass_time;
                filter_data.min_out_pass_time[selectedDoor] = min_out_pass_time;
                filter_data.change_dir_time[selectedDoor] = change_dir_time;
                filter_data.shirt_sts0_time[selectedDoor] = shirt_sts0_time;
                filter_data.filter_flags[selectedDoor] = flags;
                filter_data.max_pass_thru_closed[selectedDoor] = max_pass_thru_closed;
            }
        }

        // Обновление состояние контролов в соответствие с данными,
        // полученными из МСД и сохраненными в структуре filter_data.
        private void UpdateFilterSetTools()
        {
            try
            {
                int selectedDoor = (radioFront.Checked) ? 0 : (radioRare.Checked) ? 1 : 2;
                trackBarMinInPassTime.Value = Math.Max(trackBarMinInPassTime.Minimum,
                    Math.Min(trackBarMinInPassTime.Maximum, filter_data.min_in_pass_time[selectedDoor] * UNKNOWN_COEFF));
                trackBarMinOutPassTime.Value = Math.Max(trackBarMinOutPassTime.Minimum,
                    Math.Min(trackBarMinOutPassTime.Maximum, filter_data.min_out_pass_time[selectedDoor] * UNKNOWN_COEFF));
                trackBarReverseTime.Value = Math.Max(trackBarReverseTime.Minimum,
                    Math.Min(trackBarReverseTime.Maximum, filter_data.change_dir_time[selectedDoor] * UNKNOWN_COEFF));
                trackBarState0.Value = Math.Max(trackBarState0.Minimum,
                    Math.Min(trackBarState0.Maximum, filter_data.shirt_sts0_time[selectedDoor] * UNKNOWN_COEFF));
                trackBarMaxPass.Value = Math.Max(trackBarMaxPass.Minimum,
                    Math.Min(trackBarMaxPass.Maximum, filter_data.max_pass_thru_closed[selectedDoor]));
                spinNumberOfDoors.Value = Math.Max(spinNumberOfDoors.Minimum,
                    Math.Min(spinNumberOfDoors.Maximum, filter_data.doors));
                trackBarStsDiffTime.Value = Math.Max(trackBarStsDiffTime.Minimum,
                    Math.Min(trackBarStsDiffTime.Maximum, filter_data.stsDiffTime));
                chkMinInPassTime.Checked = ((filter_data.filter_flags[selectedDoor] & 0x01) != 0);
                chkMinOutPassTime.Checked = ((filter_data.filter_flags[selectedDoor] & 0x02) != 0);
                chkReverseTime.Checked = ((filter_data.filter_flags[selectedDoor] & 0x04) != 0);



                frontDoorFlapsNumUpDown.Value = ((filter_data.falps & 0x3)==1)?1:2;
                rearDoorFlapsNumUpDown.Value = (((filter_data.falps & 0xC)>>2) == 1) ? 1 : 2;
                extraRearDoorFlapsNumUpDown.Value = (((filter_data.falps & 0x30)>>4) == 1) ? 1 : 2;
            }
            catch (Exception)
            {
                //ErrorReport err = new ErrorReport(ex.Message + ex.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }
        }

        private void FillFilterData()
        {
            byte[] lsClone = null;

            lock (locker)
            {
                lsClone = new byte[loadedSettings.Count];
                loadedSettings.CopyTo(lsClone);
            }
            filter_data.stsDiffTime = lsClone[0];
            filter_data.falps = lsClone[1];
            filter_data.doors = lsClone[2];

            filter_data.min_in_pass_time = new byte[MAX_DOORS];
            for (int i = 0; i < MAX_DOORS; i++)
            {
                filter_data.min_in_pass_time[i] = lsClone[3 + i];
            }

            filter_data.min_out_pass_time = new byte[MAX_DOORS];
            for (int i = 0; i < MAX_DOORS; i++)
            {
                filter_data.min_out_pass_time[i] = lsClone[6 + i];
            }

            filter_data.change_dir_time = new byte[MAX_DOORS];
            for (int i = 0; i < MAX_DOORS; i++)
            {
                filter_data.change_dir_time[i] = lsClone[9 + i];
            }

            filter_data.shirt_sts0_time = new byte[MAX_DOORS];
            for (int i = 0; i < MAX_DOORS; i++)
            {
                filter_data.shirt_sts0_time[i] = lsClone[12 + i];
            }

            filter_data.filter_flags = new byte[MAX_DOORS];
            for (int i = 0; i < MAX_DOORS; i++)
            {
                filter_data.filter_flags[i] = lsClone[15 + i];
            }

            if (isOldVersion)
            {
                filter_data.Initialized = lsClone[18];
            }
            else
            {
                try
                {
                    filter_data.max_pass_thru_closed = new byte[MAX_DOORS];
                    for (int i = 0; i < MAX_DOORS; i++)
                    {
                        filter_data.max_pass_thru_closed[i] = lsClone[18 + i];
                    }

                    filter_data.Initialized = lsClone[21];
                }
                catch (Exception ex)
                {
                    ErrorReport err = new ErrorReport(ex.Message + ex.StackTrace);
                    err.LogName = "msdcpErrorReport.log";
                    err.AddToLog();
                }
            }
        }

        private void UpdateConfig()
        {
            List<string> section = new List<string>();

            section.Add(currentPortName);
            SettingsProvider.UpdateSection("[LastUsedComPort]", section);

            section.Clear();
            int windowState = (WindowState == FormWindowState.Minimized) ?
                (int)FormWindowState.Normal : (int)WindowState;
            if (WindowState == FormWindowState.Minimized)
            {
                windowState = (int)FormWindowState.Normal;

                section.Add(windowState.ToString());
                SettingsProvider.UpdateSection("[WindowState]", section);
            }
            else
            {
                section.Add(windowState.ToString());
                SettingsProvider.UpdateSection("[WindowState]", section);
                section.Clear();
                section.Add(string.Format("Width={0}", Width));
                section.Add(string.Format("Height={0}", Height));
                section.Add(string.Format("Column1={0}", portMonitor.Columns[0].FillWeight));
                section.Add(string.Format("Column2={0}", portMonitor.Columns[1].FillWeight));
                section.Add(string.Format("Column3={0}", portMonitor.Columns[2].FillWeight));
                section.Add(string.Format("Column4={0}", portMonitor.Columns[3].FillWeight));

                SettingsProvider.UpdateSection("[WindowDimensions]", section);

                section.Clear();
                section.Add(string.Format("X={0}", Location.X));
                section.Add(string.Format("Y={0}", Location.Y));
                SettingsProvider.UpdateSection("[WindowPosition]", section);
            }
        }

        private void comPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            mre_CommandSent.WaitOne();
            List<byte> temp = new List<byte>();
            byte[] buffer = new byte[512];
            int read = -1;
            int cnt = 0;
            ushort len = 0;
            try
            {
                while (comPort.BaseStream.CanRead)
                {
                    read = comPort.BaseStream.ReadByte();

                    if (read >= 0)
                    {
                        if (cnt < 512)
                        {
                            buffer[cnt] = (byte)read;
                        }
                        else
                        {
                            break;
                        }
                        // Check id and message length.
                        if (cnt == Program.LEN_MSB)
                        {
                            len = (ushort)((ushort)(buffer[Program.LEN_MSB] << 8) | ((ushort)buffer[Program.LEN_LSB]));

                            // Check if we received echo.
                            if (buffer[Program.SENDER_BYTE] == (byte)Unit.ID_PC)
                            {
                                if (len > 0x114)
                                {
                                    cnt = 0;
                                    len = 0;
                                    read = -1;
                                    break;
                                }
                                while (cnt < len - 1)
                                {
                                    cnt++;
                                    read = comPort.BaseStream.ReadByte();
                                    buffer[cnt] = (byte)read;
                                }
                                cnt = 0;
                                len = 0;
                                read = -1;
                                continue;
                            }
                        }

                        if ((len == 0 && cnt < 277) || (len > 0 && cnt < len - 1))
                        {
                            cnt++;
                        }
                        else
                        {
                            cnt++;
                            break;
                        }
                    }
                    else
                    {
                        cnt++;
                        break;
                    }
                }
            }
            catch (InvalidOperationException ioe) // По недоступности порта.
            {
                //MessageBox.Show(ioe.Message + ioe.StackTrace);
                ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                err.LogName = "msdcpErrorReport.log";
                err.AddToLog();
                //Debugger.Break();
            }
            catch (TimeoutException) // По тайм-ауту.
            {

            }
            catch (System.IO.IOException ioe) // При ошибке драйвера порта (исчезновении порта из системы).
            {
                //MessageBox.Show(ioe.Message + ioe.StackTrace);
                ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                err.LogName = "msdcpErrorReport.log";
                err.AddToLog();
                //Debugger.Break();
            }
            finally
            {
                if (comPort.IsOpen)
                {
                    comPort.DiscardInBuffer();
                    comPort.DiscardOutBuffer();
                    comPort.BaseStream.Flush();
                    //Thread.Sleep(200);//VanCode
                }
                if (cnt > 0)
                {
                    byte[] ttt = new byte[cnt];
                    Array.Copy(buffer, 0, ttt, 0, cnt);
                    temp.AddRange(ttt);
                    SetResponseStatus(temp);
                }
                else
                {
                    are_ResponseProcessed.Set();
                }
            }
        }

        private void SendCommand(Command command, object args)
        {
            mre_CommandSent.Reset();
            List<byte> TxBuffer = PrepareMessage(command, args);
            responseStatus = ResponseStatus.Wait;

            try
            {
                if (comPort.IsOpen)
                {
                    comPort.Write(TxBuffer.ToArray(), 0, TxBuffer.Count);
                }
            }
            catch (System.IO.IOException ioe)
            {
                ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                err.LogName = "msdcpErrorReport.log";
                err.AddToLog();
            }
            catch (InvalidOperationException ioe) // При недоступности порта.
            {
                ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                err.LogName = "msdcpErrorReport.log";
                err.AddToLog();
            }
            catch (TimeoutException) // По тайм-ауту.
            {
                //MessageBox.Show(toe.Message + toe.StackTrace);
                //Debugger.Break();
            }
            finally
            {
                mre_CommandSent.Set();
            }
        }

        private void TrimEcho(List<byte> resp)
        {
            if (resp.Count > 0)
            {
                if (resp[0] == (byte)Unit.ID_PC ||
                    (resp[0] & 0x7f) == (byte)Unit.ID_PC)
                {
                    if (resp.Count > 2)
                    {
                        // Поймали эхо. Отсекаем его.
                        ushort echoLength = (ushort)((resp[1] << 8) | resp[2]);
                        if (resp.Count >= echoLength)
                        {
                            resp.RemoveRange(0, echoLength);
                        }
                    }
                }
            }
        }

        private void UpdateStatusBar()
        {
            try
            {
                comStatusLabel.Text = currentPortName;

                switch (sys)
                {
                    case SystemState.READY:
                        readyStatusLabel.Text = statusLabel[(int)sys];
                        readyStatusLabel.ForeColor = Color.Green;
                        readyStatusLabel.Image = global::msdcp.Properties.Resources.ready.ToBitmap();
                        break;
                    case SystemState.NOT_READY:
                        readyStatusLabel.Text = statusLabel[(int)sys];
                        readyStatusLabel.ForeColor = Color.Red;
                        readyStatusLabel.Image = global::msdcp.Properties.Resources.notready.ToBitmap();
                        break;
                }
                switch (responseStatus)
                {
                    case ResponseStatus.Accepted:
                        statusResponse.Text = "Ответ принят";
                        break;
                    case ResponseStatus.Denied:
                        statusResponse.Text = "Ответ не принят";
                        break;
                    case ResponseStatus.Ignored:
                        statusResponse.Text = "Ответ проигнорирован";
                        break;
                    case ResponseStatus.Processed:
                        statusResponse.Text = "Ответ обработан";
                        break;
                    case ResponseStatus.Processing:
                        statusResponse.Text = "Ответ обрабатывается";
                        break;
                    case ResponseStatus.Received:
                        statusResponse.Text = "Ответ получен";
                        break;
                    case ResponseStatus.Wait:
                        statusResponse.Text = "Ожидание ответа";
                        break;
                    case ResponseStatus.BrokenPackage:
                        statusResponse.Text = "Неполный пакет";
                        break;
                    case ResponseStatus.ChecksumError:
                        statusResponse.Text = "Ошибка контрольной суммы";
                        break;
                }

                if (!string.IsNullOrEmpty(busNumber))
                {
                    busNumStatusLabel.Text = busNumber;
                    versionStatusLabel.Text = msdVersion.ToString();
                }

                if (numOfTrans != 0)
                {
                    numOfTransStatusLabel.Text =
                        string.Format("Выгрузок {0} из {1}", numOfTrans,
                        ((ifsa.SJ_Start_Address - ifsa.NSRAA_Start) / 8));
                }
                else
                {
                    numOfTransStatusLabel.Text = "Выгрузок ---- из ----";
                }

                if (numOfRecords != 0)
                {
                    numOfRecsStatusLabel.Text =
                        string.Format("Записей {0} из {1}", numOfRecords,
                        ((ifsa.SJ_EndDataArea + 1 - ifsa.SJ_Start_Address) / 8));
                }
                else
                {
                    numOfRecsStatusLabel.Text = "Записей ----- из -----";
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        // Конфигурирует порт, если он не открыт.
        private void ConfigurePort()
        {
            try
            {
                comPort.BaudRate = Program.CABLE_BAUDRATE;
                comPort.DataBits = 8;
                comPort.DtrEnable = false;
                comPort.StopBits = StopBits.One;
                comPort.RtsEnable = false;
                comPort.Parity = Parity.None;
                comPort.ReadTimeout = READ_TIMEOUT;
                comPort.WriteTimeout = WRITE_TIMEOUT;
            }
            catch (InvalidOperationException)
            {
                //ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }
            catch (Exception)
            {
                //ErrorReport err = new ErrorReport(ex.Message + ex.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }
        }

        private List<byte> PrepareMessage(Command command, object args)
        {
            List<byte> message = new List<byte>();

            switch (command)
            {
                case Command.START_SESSION:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 6, 0, (byte)command });
                    break;
                case Command.END_SESSION:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 6, 0, (byte)command });
                    break;
                case Command.PC_ONLINE:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 6, 0, (byte)command });
                    break;
                case Command.SET_DEBUG_MODE:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 6, 0, (byte)command });
                    break;
                case Command.CLEAR_DEBUG_MODE:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 6, 0, (byte)command });
                    break;
                case Command.SYNC_TIME:
                    //Format:0x16, 0x00, 0x0A, SYNC_TIME, Second, Time_Hi, Time_Lo, Date_Hi, Date_Lo, CHECKSUM
                    DateTime now = DateTime.Now;
                    TimeSpan ts = now - baseDate;
                    byte second = (byte)now.Second;
                    byte timeHi = (byte)((ushort)(now.Minute + now.Hour * 60) >> 8);
                    byte timeLo = (byte)(now.Minute + now.Hour * 60);
                    byte dateHi = (byte)((ushort)ts.Days >> 8);
                    byte dateLo = (byte)ts.Days;
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 11, 0, (byte)command, second, 
                            timeHi, timeLo, dateHi, dateLo });
                    break;
                case Command.PAGE_READ:
                    //Format:0x16, 0x00, 0x07, PAGE_READ, Page_Hi, Page_Lo, CHECKSUM
                    //Answer Format:0x15, 0x01, 0x0D, PAGE_READ|COMMAND_TRUE, 264-byte DATA, CHECKSUM
                    ushort pageNumber = (ushort)args;
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 8, 0, (byte)command, 
                            (byte)(pageNumber & 0xff), (byte)(pageNumber >> 8) });
                    break;
                case Command.PAGE_WRITE:
                    //Format:0x5E 0x49, 0x0e, 0x01, PAGE_WRITE, Page_Lo, Page_Hi, 264-byte DATA, CHECKSUM
                    //Answer Format:0x49 0x5e, 0x06, 0x00, PAGE_WRITE|COMMAND_TRUE, CHECKSUM
                    List<byte> pc = new List<byte>();
                    lock (locker)
                    {
                        pc = pageContent.ToList();
                    }
                    pageNumber = (ushort)args;
                    ushort mesLen = 0;
                    mesLen = (ushort)(Page_Size_L + 8);
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, (byte)(mesLen & 0xff), 
                        (byte)(mesLen >> 8), (byte)command, 
                        (byte)(pageNumber & 0xff), (byte)(pageNumber >> 8) });
                    message.AddRange(pc);
                    break;
                case Command.EEPROM_READ:
                    // Format: 0x16, 0x00, 0x06, EEPROM_READ, Number_of_page, CHECKSUM
                    pageNumber = (ushort)args;
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 7, 0, (byte)command, (byte)pageNumber });
                    break;
                case Command.EEPROM_WRITE:
                    // Format: 0x16, 0x01, 0x06, EEPROM_WRITE, Number_of_page, CHECKSUM
                    pageNumber = (ushort)args;
                    mesLen = (ushort)(EEPROM_PAGE_SIZE + 7);
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, (byte)(mesLen & 0xff), 
                        (byte)(mesLen >> 8), (byte)command, 
                        (byte)pageNumber });
                    message.AddRange(EEPROM.GetRange((pageNumber * EEPROM_PAGE_SIZE), EEPROM_PAGE_SIZE));
                    break;
                case Command.READ_BUS_NUMBER:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 6, 0, (byte)command });
                    break;
                case Command.WRITE_BUS_NUMBER:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, BN_STRING_LEN + 6, 0, (byte)command });
                    byte[] bn = (EEPROM.GetRange(0, BN_STRING_LEN)).ToArray();
                    message.AddRange(bn);
                    break;
                case Command.RS485_SWITCH:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 6, 0, (byte)command });
                    break;
                case Command.ERASE_INT_FLASH:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 6, 0, (byte)command });
                    break;
                case Command.SEND_CONFIG:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 6, 0, (byte)command });
                    break;
                case Command.SEND_INTERVAL_DATA:
                    object[] argVec = (object[])args;
                    byte[] pDateTime1 = (byte[])argVec[0];
                    byte[] pDateTime2 = (byte[])argVec[1];
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 14, 0, (byte)command });
                    message.AddRange(pDateTime1);
                    message.AddRange(pDateTime2);
                    message.AddRange(new byte[] { (byte)(Program.MTU_SIZE & 0xff), (byte)((Program.MTU_SIZE >> 8) & 0xff) });
                    break;
                case Command.DATA_CORRECT:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 6, 0, (byte)command });
                    break;
                case Command.DATA_INCORRECT:
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 6, 0, (byte)command });
                    break;
                case Command.UPGRADE:
                    argVec = (object[])args;
                    uint version = (uint)argVec[0];
                    uint size = (uint)argVec[1];
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 14, 0, (byte)command }); 
                    message.AddRange(BitConverter.GetBytes(version));
                    message.AddRange(BitConverter.GetBytes(size));
                    break;
                case Command.UPGRADE_BLOCK:
                    byte[] upgradeBlock = (byte[])args;
                    ushort len = (ushort)(6 + upgradeBlock.Length);
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 
                        (byte)(len & 0xff), (byte)((len >> 8) & 0xff),
                        (byte)command });
                    message.AddRange(upgradeBlock);
                    break;
                case Command.UPGRADE_LAST_BLOCK:
                    upgradeBlock = (byte[])args;
                    len = (ushort)(6 + upgradeBlock.Length);
                    message.AddRange(new byte[] { (byte)Unit.ID_MSD, (byte)Unit.ID_PC, 
                        (byte)(len & 0xff), (byte)((len >> 8) & 0xff),
                        (byte)command });
                    message.AddRange(upgradeBlock);
                    break;
            }

            // Подсчет контрольной суммы.
            byte cs = 0;
            cs = ChecksumCalc(message);

            message.Add(cs);

            return message;
        }


        private byte OldChecksumCalc(byte[] pData, ushort len)
        {
            byte CS = 0;
            byte tmp = 0;

            for (int i = 0; i < (len - 1); ++i)
            {
                CS += pData[i];
            }

            if (len >= CS)
            {
                tmp = (byte)(len - CS);
            }
            else
            {
                tmp = (byte)(0xff - CS + len + 1);
            }

            return tmp;
        }


        private byte ChecksumCalcOld(List<byte> message)
        {
            byte cs = 0;
            for (int i = 0; i < message.Count; i++)
            {
                cs ^= message[i];
            }
            cs = (byte)(~cs);
            return cs;
        }

        private byte ChecksumCalc(List<byte> message)
        {
            byte cs = 0;
            for (int i = 0; i < message.Count; i++)
            {
                cs = CRC8(message[i], cs);
            }
            return cs;
        }

        private byte CRC8(byte data, byte crc)
        {
            crc ^= data;

            for (byte i = 0; i < 8; ++i)
            {
                crc = (byte)(crc & (byte)0x80) != (byte)0 ?
                    (byte)((crc << (byte)1) ^ (byte)0x31) :
                    (byte)(crc << (byte)1);
            }

            return crc;
        }

        private void PrepareFiltersForUploading()
        {
            uploadingSettings = new List<byte>();

            uploadingSettings.Add(filter_data.doors);

            for (int i = 0; i < MAX_DOORS; i++)
            {
                uploadingSettings.Add(filter_data.min_in_pass_time[i]);
            }

            for (int i = 0; i < MAX_DOORS; i++)
            {
                uploadingSettings.Add(filter_data.min_out_pass_time[i]);
            }

            for (int i = 0; i < MAX_DOORS; i++)
            {
                uploadingSettings.Add(filter_data.change_dir_time[i]);
            }

            for (int i = 0; i < MAX_DOORS; i++)
            {
                uploadingSettings.Add(filter_data.shirt_sts0_time[i]);
            }

            for (int i = 0; i < MAX_DOORS; i++)
            {
                uploadingSettings.Add(filter_data.filter_flags[i]);
            }

            if (!isOldVersion)
            {
                for (int i = 0; i < MAX_DOORS; i++)
                {
                    uploadingSettings.Add(filter_data.max_pass_thru_closed[i]);
                }
            }

            uploadingSettings.Add(filter_data.Initialized);
        }

        private void timer_OnElapsed(object sender, ElapsedEventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            ReinitializeConnection();
        }


        private void exitMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            FreeWorker();

            OnUpdateViewState -= MainForm_OnUpdateViewState;

            if (!string.IsNullOrEmpty(currentPortName) && comPort.IsOpen)
            {
                SendCommand(Command.END_SESSION, null);
                are_ResponseProcessed.WaitOne(1000);

                try
                {
                    if (comPort.IsOpen)
                    {
                        comPort.Close();
                    }
                }
                catch (System.IO.IOException)
                {
                    //ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                    //err.LogName = "msdcpErrorReport.log";
                    //err.AddToLog();
                    //MessageBox.Show(ioe.Message);
                }
                catch { }
            }

            UpdateConfig();
            SettingsProvider.WriteSettings();
        }

        private void diagnosticMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            if (sys == SystemState.READY)
            {
                if (diagnosticMenuItem.CheckState == CheckState.Unchecked)
                {
                    if (comPort.IsOpen)
                    {
                        diagnosticMenuItem.CheckState = CheckState.Checked;
                        toolButtonDiagnostic.CheckState = CheckState.Checked;
                        SendCommand(Command.SET_DEBUG_MODE, null);
                        are_ResponseProcessed.WaitOne(1000);
                    }
                }
                else
                {
                    if (comPort.IsOpen)
                    {
                        diagnosticMenuItem.CheckState = CheckState.Unchecked;
                        toolButtonDiagnostic.CheckState = CheckState.Unchecked;
                        SendCommand(Command.CLEAR_DEBUG_MODE, null);
                        are_ResponseProcessed.WaitOne(1000);
                    }
                }
            }
        }

        private void selectPortMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }
            try
            {
                SelectPortDialog spd = new SelectPortDialog(SerialPort.GetPortNames());

                if (spd.ShowDialog() == DialogResult.OK)
                {
                    if (comPort.PortName != spd.SelectedPort)
                    {
                        numOfRecords = 0;
                        numOfTrans = 0;

                        if (comPort.IsOpen)
                        {
                            timer.Stop();
                            SendCommand(Command.END_SESSION, null);
                            are_ResponseProcessed.WaitOne(500);
                            comPort.Close();
                        }

                        comPort.PortName = spd.SelectedPort;
                        ConfigurePort();
                        SerialPortFixer.Execute(spd.SelectedPort);
                    }

                    if (!comPort.IsOpen)
                    {
                        comPort.Open();
                    }

                    sys = SystemState.READY;
                    GC.SuppressFinalize(comPort.BaseStream);
                    currentPortName = spd.SelectedPort;
                    UpdateConfig();
                    UpdateStatusBar();
                }
            }
            catch (System.IO.IOException) // При отсутствии порта
            {
                //ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //MessageBox.Show(ioe.Message);
                //Debugger.Break();
            }
            catch { }
        }

        private void showHEXMenuItem_Click(object sender, EventArgs e)
        {
            if (showHEXMenuItem.CheckState == CheckState.Unchecked)
            {
                showHEXMenuItem.CheckState = CheckState.Checked;
                needToViewHex = true;
            }
            else
            {
                showHEXMenuItem.CheckState = CheckState.Unchecked;
                needToViewHex = false;
            }
        }

        private void timeSyncMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }
            try
            {
                if (comPort.IsOpen)
                {
                    SendCommand(Command.SYNC_TIME, null);
                    are_ResponseProcessed.WaitOne(1000);
                }
            }
            catch (System.IO.IOException) // При отсутствии порта
            {
                //ErrorReport err = new ErrorReport(ioe.Message + ioe.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //MessageBox.Show(ioe.Message);
                //Debugger.Break();
            }
            catch { }
        }


        private void trackBarMinInPassTime_Scroll(object sender, EventArgs e)
        {
            txtMinInPassTime.Text = trackBarMinInPassTime.Value.ToString();
        }

        private void trackBarMinOutPassTime_Scroll(object sender, EventArgs e)
        {
            txtMinOutPassTime.Text = trackBarMinOutPassTime.Value.ToString();
        }

        private void trackBarReverseTime_Scroll(object sender, EventArgs e)
        {
            txtReverseTime.Text = trackBarReverseTime.Value.ToString();
        }

        private void trackBarState0_Scroll(object sender, EventArgs e)
        {
            txtState0.Text = trackBarState0.Value.ToString();
        }

        private void txtMinInPassTime_TextChanged(object sender, EventArgs e)
        {
            int value;
            bool fOK = int.TryParse(txtMinInPassTime.Text, out value);
            if (fOK)
            {
                value = Math.Min(trackBarMinInPassTime.Maximum,
                    Math.Max(trackBarMinInPassTime.Minimum, value));
                txtMinInPassTime.Text = value.ToString();
                trackBarMinInPassTime.Value = value;
            }
            else
            {
                txtMinInPassTime.Text = trackBarMinInPassTime.Value.ToString();
            }
        }



        private void txtMinOutPassTime_TextChanged(object sender, EventArgs e)
        {
            int value;
            bool fOK = int.TryParse(txtMinOutPassTime.Text, out value);
            if (fOK)
            {
                value = Math.Min(trackBarMinOutPassTime.Maximum,
                    Math.Max(trackBarMinOutPassTime.Minimum, value));
                txtMinOutPassTime.Text = value.ToString();
                trackBarMinOutPassTime.Value = value;
            }
            else
            {
                txtMinOutPassTime.Text = trackBarMinOutPassTime.Value.ToString();
            }
        }

        private void txtReverseTime_TextChanged(object sender, EventArgs e)
        {
            int value;
            bool fOK = int.TryParse(txtReverseTime.Text, out value);
            if (fOK)
            {
                value = Math.Min(trackBarReverseTime.Maximum,
                    Math.Max(trackBarReverseTime.Minimum, value));
                txtReverseTime.Text = value.ToString();
                trackBarReverseTime.Value = value;
            }
            else
            {
                txtReverseTime.Text = trackBarReverseTime.Value.ToString();
            }
        }

        private void txtState0_TextChanged(object sender, EventArgs e)
        {
            int value;
            bool fOK = int.TryParse(txtState0.Text, out value);
            if (fOK)
            {
                value = Math.Min(trackBarState0.Maximum,
                    Math.Max(trackBarState0.Minimum, value));
                txtState0.Text = value.ToString();
                trackBarState0.Value = value;
            }
            else
            {
                txtState0.Text = trackBarState0.Value.ToString();
            }
        }

        private void chkApplyToAll_CheckedChanged(object sender, EventArgs e)
        {
            if (chkApplyToAll.CheckState == CheckState.Checked)
            {
                radioFront.Enabled = false;
                radioRare.Enabled = false;
                radioRare1.Enabled = false;
                //grpDoorsSwitch.Enabled = false;
            }
            else
            {
                radioFront.Enabled = true;
                radioRare.Enabled = true;
                radioRare1.Enabled = true;
                //grpDoorsSwitch.Enabled = true;
            }
        }

        private void writeSettingsMenuItem_Click(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(WriteFilterSettings), null);
        }

        private List<byte> FilterDataToList(IntF_Filter_Area_t_182 fd)
        {
            List<byte> coll = new List<byte>();
            coll.Add(fd.stsDiffTime);
            coll.Add(fd.falps);
            coll.Add(fd.doors);

            coll.AddRange(fd.min_in_pass_time);
            coll.AddRange(fd.min_out_pass_time);
            coll.AddRange(fd.change_dir_time);
            coll.AddRange(fd.shirt_sts0_time);
            coll.AddRange(fd.filter_flags);
            if (!isOldVersion)
            {
                coll.AddRange(fd.max_pass_thru_closed);
            }

            // Фильтры инициализированы.
            coll.Add(0xCC);
            
            return coll;
        }

        private void readSettingsMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            try
            {
                if (comPort.IsOpen)
                {
                    ReadFilterData();
                }
            }
            catch (Exception)
            {
                //MessageBox.Show(ex.Message + ex.StackTrace);
                //Debugger.Break();
            }
        }


        private void ReadFilterData()
        {
            pageToRead = 0;
            pageContent.Clear();
            SendCommand(Command.PAGE_READ, pageToRead);
            are_ResponseProcessed.WaitOne(1000);
        }

        // Этот метод управляет логикой доступности переключателей дверей
        // в зависимости от их общего количества.
        private void spinNumberOfDoors_ValueChanged(object sender, EventArgs e)
        {
            filter_data.doors = (byte)spinNumberOfDoors.Value;
            switch ((int)spinNumberOfDoors.Value)
            {
                case 1:
                    radioFront.Checked = true;
                    radioRare.Enabled = false;
                    radioRare1.Enabled = false;

                    frontDoorFlapsNumUpDown.Enabled = true;
                    rearDoorFlapsNumUpDown.Enabled = false;
                    extraRearDoorFlapsNumUpDown.Enabled = false;
                    break;
                case 2:
                    radioRare.Enabled = true;
                    radioRare.Checked = radioRare1.Checked;
                    radioRare1.Enabled = false;

                    frontDoorFlapsNumUpDown.Enabled = true;
                    rearDoorFlapsNumUpDown.Enabled = true;
                    extraRearDoorFlapsNumUpDown.Enabled = false;
                    break;
                case 3:
                    radioRare1.Enabled = true;
                    frontDoorFlapsNumUpDown.Enabled = true;
                    rearDoorFlapsNumUpDown.Enabled = true;
                    extraRearDoorFlapsNumUpDown.Enabled = true;
                    break;
            }
        }

        // Сохраняем настройки по передней двери прежде чем перейти к другой 
        // или загружаем сохраненные ранее при переходе к передней двери.
        private void radioFront_CheckedChanged(object sender, EventArgs e)
        {
            if (radioFront.Checked)
            {
                UpdateFilterSetTools();
            }
            else
            {
                SaveToolSet(0);
            }
        }

        private void SaveToolSet(int selectedDoor)
        {
            filter_data.min_in_pass_time[selectedDoor] = (byte)(trackBarMinInPassTime.Value / UNKNOWN_COEFF);
            filter_data.min_out_pass_time[selectedDoor] = (byte)(trackBarMinOutPassTime.Value / UNKNOWN_COEFF);
            filter_data.change_dir_time[selectedDoor] = (byte)(trackBarReverseTime.Value / UNKNOWN_COEFF);
            filter_data.shirt_sts0_time[selectedDoor] = (byte)(trackBarState0.Value / UNKNOWN_COEFF);

            filter_data.filter_flags[selectedDoor] = 0;
            filter_data.filter_flags[selectedDoor] |= chkMinInPassTime.Checked ? (byte)(0x01) : (byte)0;
            filter_data.filter_flags[selectedDoor] |= chkMinOutPassTime.Checked ? (byte)(0x02) : (byte)0;
            filter_data.filter_flags[selectedDoor] |= chkReverseTime.Checked ? (byte)(0x04) : (byte)0;
            filter_data.filter_flags[selectedDoor] |= chkState0.Checked ? (byte)(0x08) : (byte)0;

            filter_data.max_pass_thru_closed[selectedDoor] = (byte)(trackBarMaxPass.Value);
        }

        // Сохраняем настройки по задней двери прежде чем перейти к другой 
        // или загружаем сохраненные ранее при переходе к задней двери.
        private void radioRare_CheckedChanged(object sender, EventArgs e)
        {
            if (radioRare.Checked)
            {
                UpdateFilterSetTools();
            }
            else
            {
                SaveToolSet(1);
            }
        }

        // Сохраняем настройки по задней доп двери прежде чем перейти к другой 
        // или загружаем сохраненные ранее при переходе к задней доп двери.
        private void radioRare1_CheckedChanged(object sender, EventArgs e)
        {
            if (radioRare1.Checked)
            {
                UpdateFilterSetTools();
            }
            else
            {
                SaveToolSet(2);
            }
        }

        private void readEEPROMMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }

            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(ReadEEPROM);
            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ReadingEepromCompleted);
            worker.WorkerReportsProgress = true;
            worker.WorkerSupportsCancellation = true;

            progressBar.Style = ProgressBarStyle.Blocks;
            ShowProgressBar();

            EEPROM.Clear();
            cancel = false;
            toolButtonCancel.Enabled = true;

            triadStates[0] = TriadState.Normal;
            triadStates[1] = TriadState.Normal;
            triadStates[2] = TriadState.Normal;
            for (int i = 0; i < 3; i++)
            {
                HighlightTriadLabel(i);
            }

            worker.RunWorkerAsync();
        }

        private void ReadEEPROM(object sender, DoWorkEventArgs e)
        {
            // Останавливаем таймер, чтобы он не помешал передаче данных.
            timer.Enabled = false;

            try
            {
                int attempts = MAX_ATTEMPTS;// Number of attempts for reading each EEPROM page.


                for (int pageNumber = 0; pageNumber < EEPROM_TOTAL_PAGES; pageNumber++)
                {
                    if (cancel) break;

                    SendCommand(Command.EEPROM_READ, (ushort)pageNumber);
                    are_ResponseProcessed.WaitOne(1000);

                    if (responseStatus != ResponseStatus.Accepted)
                    {
                        attempts--;
                        if (attempts <= 0)
                        {
                            attempts = MAX_ATTEMPTS;
                        }
                        else
                        {
                            pageNumber--;
                        }
                    }
                    else
                    {
                        attempts = MAX_ATTEMPTS;
                        worker.ReportProgress((progressBar.Maximum / EEPROM_TOTAL_PAGES) * (pageNumber + 1));
                    }
                }


            }
            catch (Exception)
            {
                //ErrorReport err = new ErrorReport(ex.Message + ex.StackTrace);
                //err.LogName = "msdcpErrorReport.log";
                //err.AddToLog();
                //Debugger.Break();
            }
            finally
            {
                // Вновь активируем таймер.
                timer.Enabled = true;
            }
        }

        private void ReadingEepromCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            HideProgressBar();
            
            progressBar.Style = ProgressBarStyle.Marquee;

            FreeWorker();

            toolButtonCancel.Enabled = false;
            if (cancel)
            {
                cancel = false;
                return;
            }

            // Если успешно прочитали данные...
            if (EEPROM.Count == EEPROM_FULL_SIZE)
            {
                busNumber = GetBusNumber();
                GetMsdVersion();
                if (msdVersion.Build < 182)
                {
                    isOldVersion = true;
                    filterDataSize = FILTER_SIZE_BASE;
                    Page_Size_L = PAGE_SIZE;
                }
                if (msdVersion.Build > 206)
                {
                    filterDataSize = FILTER_SIZE_182 + 1;
                    Page_Size_L = PAGE_SIZE + 1;
                }
                else
                {
                    isOldVersion = false;
                    filterDataSize = FILTER_SIZE_182;
                    Page_Size_L = PAGE_SIZE;
                }
                filter_data.doors = (byte)msdVersion.Doors;
                ushort sysStat = (ushort)((EEPROM[EEPROM_FULL_SIZE - 3] << 8) | EEPROM[EEPROM_FULL_SIZE - 4]);
                trackerControlMenuItem.Checked = (sysStat & (1 << _SYS_STAT_NEED_CHECK_TRACKER_BIT)) != 0 ? true : false;
                ReadFilterData();
                UpdateFilterSetTools();
                FillEEPROMGrid();

                SendCommand(Command.SYNC_TIME, null);
                are_ResponseProcessed.WaitOne(1000);

                UpdateStatusBar();

            }
        }

        private void GetMsdVersion()
        {
            ushort msdVer = (ushort)((EEPROM[EEPROM_FULL_SIZE - 1] << 8) | EEPROM[EEPROM_FULL_SIZE - 2]);
            msdVersion = new MsdVersion(msdVer);
        }

        private string GetBusNumber()
        {
            string bn = string.Empty;

            Encoding enc = Encoding.ASCII;
            byte[] buffer = new byte[BN_STRING_LEN];
            EEPROM.CopyTo(0, buffer, 0, BN_STRING_LEN);
            bn = enc.GetString(buffer);
            bn = bn.TrimStart(' ', '\0');
            bn = bn.TrimEnd(' ', '\0');

            return bn;
        }

        private void changeBusNumMenuItem_Click(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(ChangeBusNumber));
        }

        private void ChangeBusNumber(object args)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            SendCommand(Command.READ_BUS_NUMBER, null);
            are_ResponseProcessed.WaitOne(1000);

            BusNumberDialog bnd = new BusNumberDialog(busNumber);
            if (bnd.ShowDialog() == DialogResult.OK)
            {
                busNumber = bnd.BusNumber;

                Encoding enc = Encoding.ASCII;
                int cb = enc.GetByteCount(busNumber);
                byte[] buffer = new byte[BN_STRING_LEN];
                enc.GetBytes(busNumber, 0, cb, buffer, 1);
                // Первым  и последним символом в номере автобуса должен быть пробел
                // (для совместимости с БУ и предыдущими версиями МСД).
                buffer.SetValue((byte)0x20, 0);
                buffer.SetValue((byte)0x20, BN_STRING_LEN - 2);
                ///////////////////////////////////////////////////////
                if (EEPROM.Count == EEPROM_FULL_SIZE)
                {
                    EEPROM.RemoveRange(0, buffer.Length);
                    EEPROM.InsertRange(0, buffer);
                }
                SendCommand(Command.WRITE_BUS_NUMBER, null);
                are_ResponseProcessed.WaitOne(1000);
            }
        }

        private void SaveEEPROM()
        {
            FillEEPROMGrid();

            SaveFileDialog sfd = new SaveFileDialog();
            sfd.AddExtension = true;
            sfd.DefaultExt = ".eei";
            sfd.FileName = string.Format("{0}_{1}_EEPROM.eei", busNumber, DateTime.Now.ToShortDateString());
            sfd.Filter = "Файлы образов EEPROM|*.eei|Все файлы|*.*";
            sfd.FilterIndex = 1;
            sfd.Title = "Сохранение образа EEPROM";
            sfd.CheckPathExists = true;
            if (sfd.ShowDialog() == DialogResult.OK)
            {
                FileStream fs = null;
                try
                {
                    fs = File.Create(sfd.FileName);
                    fs.Write(EEPROM.ToArray(), 0, EEPROM_FULL_SIZE);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + ex.StackTrace);
                }
                finally
                {
                    if (fs != null)
                    {
                        fs.Close();
                        fs.Dispose();
                    }
                }
            } // if DialogResult.OK
        }

        private void FillEEPROMGrid()
        {
            eepromGrid.Visible = false;

            eepromGrid.Rows.Clear();

            // Заполнение eepromGrid.
            for (int row = 0; row < (EEPROM_FULL_SIZE / cellsPerRow); row++)
            {
                string[] content = new string[cellsPerRow + 2];
                byte[] values = new byte[cellsPerRow];
                content[0] = string.Format("{0:X4}:", row * cellsPerRow);
                values = (EEPROM.GetRange((row * cellsPerRow), cellsPerRow)).ToArray();
                Encoding enc = Encoding.Default;
                content[cellsPerRow + 1] = enc.GetString(values);

                for (int i = 0; i < cellsPerRow; i++)
                {
                    content[i + 1] = string.Format("{0:X2}", values[i]);
                }
                eepromGrid.Rows.Add(content);
            }// for(...)
            eepromGrid.Visible = true;

            // Disabling value change event for combo box with triad indexes during analyzing EEPROM.
            comboTriadIndex.SelectedIndexChanged -= comboTriadIndex_SelectedIndexChanged;
            AnalyzeEEPROM();
            comboTriadIndex.SelectedIndexChanged += comboTriadIndex_SelectedIndexChanged;
        }

        /// <summary>
        /// Analyzes EEPROM memory image: 
        /// detecting the current triad and decode it;
        /// searching for cells, that could be a damaged 
        /// triads (triad is a triple date/time record).
        /// </summary>
        private void AnalyzeEEPROM()
        {
            if (EEPROM.Count == EEPROM_FULL_SIZE)
            {
                // First we are detecting a current triad.
                // 2 - number of cells that contains current triad index (size of index)
                currIndex = GetTriadIndex();
                comboTriadIndex.SelectedIndex = currIndex;
                // 9 - number of cells that contains one full triad (size of triad).
                currTriadAddress = (ushort)(EEPROM_TRIAD_ARRAY_ADDRESS + currIndex * 9);

                byte[] packedDT1 = EEPROM.GetRange(currTriadAddress, 3).ToArray();
                DateTime dt1 = UnpackDateTime(packedDT1);
                lblDate1.Text = dt1.ToShortDateString();
                lblTime1.Text = dt1.ToString(timePattern);

                byte[] packedDT2 = EEPROM.GetRange(currTriadAddress + 3, 3).ToArray();
                dt1 = UnpackDateTime(packedDT2);
                lblDate2.Text = dt1.ToShortDateString();
                lblTime2.Text = dt1.ToString(timePattern);

                byte[] packedDT3 = EEPROM.GetRange(currTriadAddress + 6, 3).ToArray();
                dt1 = UnpackDateTime(packedDT3);
                lblDate3.Text = dt1.ToShortDateString();
                lblTime3.Text = dt1.ToString(timePattern);

                HighlightTriadCells(0, currTriadAddress);
                HighlightTriadCells(1, currTriadAddress);
                HighlightTriadCells(2, currTriadAddress);
            }
        }

        private void HighlightTriadCells(ushort activeLabelIndex, ushort currTriadAddress)
        {
            int rowIndex = 0;
            int columnIndex = 0;

            if (triadStates[activeLabelIndex] == TriadState.Highlighted)
            {
                GetGridLocation(currTriadAddress + activeLabelIndex * 3, out rowIndex, out columnIndex);
                eepromGrid[columnIndex, rowIndex].Style = highlightedCellStyle;
                eepromGrid.InvalidateCell(columnIndex, rowIndex);

                GetGridLocation(currTriadAddress + activeLabelIndex * 3 + 1, out rowIndex, out columnIndex);
                eepromGrid[columnIndex, rowIndex].Style = highlightedCellStyle;
                eepromGrid.InvalidateCell(columnIndex, rowIndex);

                GetGridLocation(currTriadAddress + activeLabelIndex * 3 + 2, out rowIndex, out columnIndex);
                eepromGrid[columnIndex, rowIndex].Style = highlightedCellStyle;
                eepromGrid.InvalidateCell(columnIndex, rowIndex);
            }
            else
            {
                GetGridLocation(currTriadAddress + activeLabelIndex * 3, out rowIndex, out columnIndex);
                eepromGrid[columnIndex, rowIndex].Style = (rowIndex % 2) != 0 ?
                    eepromGrid.AlternatingRowsDefaultCellStyle : eepromGrid.DefaultCellStyle;
                eepromGrid.InvalidateCell(columnIndex, rowIndex);

                GetGridLocation(currTriadAddress + activeLabelIndex * 3 + 1, out rowIndex, out columnIndex);
                eepromGrid[columnIndex, rowIndex].Style = (rowIndex % 2) != 0 ?
                    eepromGrid.AlternatingRowsDefaultCellStyle : eepromGrid.DefaultCellStyle;
                eepromGrid.InvalidateCell(columnIndex, rowIndex);

                GetGridLocation(currTriadAddress + activeLabelIndex * 3 + 2, out rowIndex, out columnIndex);
                eepromGrid[columnIndex, rowIndex].Style = (rowIndex % 2) != 0 ?
                    eepromGrid.AlternatingRowsDefaultCellStyle : eepromGrid.DefaultCellStyle;
                eepromGrid.InvalidateCell(columnIndex, rowIndex);
            }

            if (eepromGrid.Visible && eepromGrid.Rows[rowIndex].Displayed == false)
            {
                eepromGrid.FirstDisplayedScrollingRowIndex = rowIndex;
            }
        }

        private DateTime UnpackDateTime(byte[] packedDT)
        {
            DateTime result = baseDate;
            ushort date = (ushort)(packedDT[0] << 4);
            date |= (ushort)((packedDT[1] >> 4) & 0xff);
            result = result.AddDays(date);
            ushort time = (ushort)((packedDT[1] & 0x0f) << 8);
            time |= (ushort)(packedDT[2]);
            result = result.AddMinutes(time);
            return result;
        }

        private void saveEEPROMMenuItem_Click(object sender, EventArgs e)
        {
            if (EEPROM.Count == EEPROM_FULL_SIZE)
            {
                this.BeginInvoke(OnSaveEEPROM);
            }
        }

        private void lblTriadIndex_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                if (lblTriadIndex.BorderStyle == BorderStyle.None)
                {
                    lblTriadIndex.BorderStyle = BorderStyle.FixedSingle;
                    lblTriadIndex.BackColor = Color.HotPink;
                    lblTriadIndex.ForeColor = Color.Yellow;
                    int rowIndex = 0;
                    int columnIndex = 0;
                    GetGridLocation(EEPROM_TRIAD_INDEX_ADDRESS, out rowIndex, out columnIndex);
                    eepromGrid[columnIndex, rowIndex].Style = highlightedCellStyle;
                    eepromGrid[columnIndex + 1, rowIndex].Style = highlightedCellStyle;
                }
                else
                {
                    lblTriadIndex.BorderStyle = BorderStyle.None;
                    lblTriadIndex.BackColor = Color.Transparent;
                    lblTriadIndex.ForeColor = SystemColors.ControlText;
                    int rowIndex = 0;
                    int columnIndex = 0;
                    GetGridLocation(EEPROM_TRIAD_INDEX_ADDRESS, out rowIndex, out columnIndex);
                    eepromGrid[columnIndex, rowIndex].Style = eepromGrid.AlternatingRowsDefaultCellStyle;
                    eepromGrid[columnIndex + 1, rowIndex].Style = eepromGrid.AlternatingRowsDefaultCellStyle;
                }
            }
        }

        private void GetGridLocation(int eepromCellIndex, out int rowIndex, out int columnIndex)
        {
            rowIndex = eepromCellIndex / 0x10;
            columnIndex = (eepromCellIndex % 0x10) + 1;
        }



        private void loadEepromMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.AddExtension = true;
            ofd.CheckFileExists = true;
            ofd.CheckPathExists = true;
            ofd.DefaultExt = ".eei";
            ofd.Filter = "Файлы образов EEPROM|*.eei|Все файлы|*.*";
            ofd.FilterIndex = 1;
            ofd.Multiselect = false;
            ofd.Title = "Открытие файла образа EEPROM";
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                string filename = ofd.FileName;
                FileStream fs = null;
                try
                {
                    fs = File.OpenRead(filename);
                    byte[] buffer = new byte[EEPROM_FULL_SIZE];
                    int bytesRead = fs.Read(buffer, 0, EEPROM_FULL_SIZE);
                    if (bytesRead != EEPROM_FULL_SIZE)
                    {
                        MessageBox.Show("Ошибка чтения файла образа EEPROM!");
                        return;
                    }
                    EEPROM.Clear();
                    EEPROM.AddRange(buffer);
                    this.BeginInvoke(OnFillGrid);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + ex.StackTrace);
                }
                finally
                {
                    fs.Close();
                    fs.Dispose();
                }
            }
        }

        private void storeEepromMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            if (EEPROM.Count == EEPROM_FULL_SIZE)
            {
                DialogResult res = MessageBox.Show("Это действие приведет к изменению" +
                    " имеющихся в EEPROM данных. Продолжить?",
                    "Предупреждение", MessageBoxButtons.YesNo,
                    MessageBoxIcon.Exclamation);
                if (res == DialogResult.Yes)
                {

                    worker = new BackgroundWorker();
                    worker.DoWork += new DoWorkEventHandler(StoreEEPROM);
                    worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
                    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(StoringEepromCompleted);
                    worker.WorkerReportsProgress = true;
                    worker.WorkerSupportsCancellation = true;

                    progressBar.Style = ProgressBarStyle.Blocks;
                    ShowProgressBar();

                    cancel = false;
                    toolButtonCancel.Enabled = true;

                    worker.RunWorkerAsync();
                }
            }
        }

        private void StoreEEPROM(object sender, DoWorkEventArgs e)
        {
            timer.Enabled = false;

            try
            {
                for (int pageNumber = 0; pageNumber < EEPROM_TOTAL_PAGES; pageNumber++)
                {
                    if (cancel) break;

                    SendCommand(Command.EEPROM_WRITE, (ushort)pageNumber);
                    are_ResponseProcessed.WaitOne(10000);


                    if (responseStatus != ResponseStatus.Accepted)
                    {
                        pageNumber--;
                    }
                    else
                    {
                        worker.ReportProgress((progressBar.Maximum / EEPROM_TOTAL_PAGES) * (pageNumber + 1));
                    }
                }
            }
            catch (Exception)
            {
                //Debugger.Break();
            }
            finally
            {
                timer.Enabled = true;
            }
        }

        private void StoringEepromCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            cancel = false;
            toolButtonCancel.Enabled = false;

            HideProgressBar();
            progressBar.Style = ProgressBarStyle.Marquee;

            FreeWorker();

            UpdateStatusBar();
        }

        private void eepromGrid_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            string newEepromValueString = eepromGrid[e.ColumnIndex, e.RowIndex].EditedFormattedValue.ToString();
            char[] cRep = newEepromValueString.ToArray();
            int[] digit = new int[cRep.Length];

            byte result = 0;

            for (int i = 0; i < cRep.Length; i++)
            {
                if (char.IsDigit(cRep[i]))
                {
                    // Если цифра.
                    digit[i] = int.Parse(cRep[i].ToString());
                }
                else if (char.IsLetter(cRep[i]))
                {
                    // Если буква.
                    char c = char.ToUpper(cRep[i]);
                    if (c >= 'A' && c <= 'F')
                    {
                        digit[i] = (int)c - 0x37;
                    }
                }
                else
                {
                    // Если не цифра и не буква.
                    digit[i] = 0;
                }
                result |= (byte)(digit[i] << (4 * (cRep.Length - 1 - i)));
            }

            EEPROM[(e.RowIndex * 16) + (e.ColumnIndex - 1)] = result;
            FillEEPROMGrid();
        }



        private void restartMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            ReinitializeConnection();
        }

        private void TriadSingleClickProcessing(int activeLabelIndex)
        {
            // Here we do operations on a mouse single click.
            if (activeLabelIndex >= 0)
            {
                // If current state of triad is normal...
                if (triadStates[activeLabelIndex] == TriadState.Normal)//triadDateLabel[activeLabelIndex].BorderStyle == BorderStyle.None)
                {
                    triadStates[activeLabelIndex] = TriadState.Highlighted;
                }
                else // if current state of triad is highlighted...
                {
                    triadStates[activeLabelIndex] = TriadState.Normal;
                }

                HighlightTriadLabel(activeLabelIndex);
                HighlightTriadCells((ushort)activeLabelIndex, currTriadAddress);
            }
        }

        private void HighlightTriadLabel(int activeLabelIndex)
        {
            if (triadStates[activeLabelIndex] == TriadState.Highlighted)
            {
                triadDateLabel[activeLabelIndex].BorderStyle = BorderStyle.FixedSingle;
                triadTimeLabel[activeLabelIndex].BorderStyle = BorderStyle.FixedSingle;
                triadDateLabel[activeLabelIndex].BackColor = Color.HotPink;
                triadTimeLabel[activeLabelIndex].BackColor = Color.HotPink;
                triadDateLabel[activeLabelIndex].ForeColor = Color.Yellow;
                triadTimeLabel[activeLabelIndex].ForeColor = Color.Yellow;
            }
            else
            {

                triadDateLabel[activeLabelIndex].BorderStyle = BorderStyle.None;
                triadTimeLabel[activeLabelIndex].BorderStyle = BorderStyle.None;
                triadDateLabel[activeLabelIndex].BackColor = Color.Transparent;
                triadTimeLabel[activeLabelIndex].BackColor = Color.Transparent;
                triadDateLabel[activeLabelIndex].ForeColor = SystemColors.ControlText;
                triadTimeLabel[activeLabelIndex].ForeColor = SystemColors.ControlText;
            }
        }


        private void lblDate1_MouseClick(object sender, MouseEventArgs e)
        {
            // Processing only right mouse button single click.
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                TriadSingleClickProcessing(0);
            }
        }

        private void lblDate1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            // Processing only left mouse button double click.
            if (e.Button == System.Windows.Forms.MouseButtons.Left && e.Clicks == 2)
            {
                SelectDateTimeDialog sdtd = new SelectDateTimeDialog();
                DateTime dt = DateTime.Parse(lblDate1.Text);
                TimeSpan ts = TimeSpan.Parse(lblTime1.Text);
                dt += ts;
                sdtd.DateTime = dt;
                if (sdtd.ShowDialog() == DialogResult.OK)
                {
                    dt = sdtd.DateTime;
                    byte[] packedDT = PackDateTime(dt);
                    ushort triadIndex = GetTriadIndex();
                    int offset = EEPROM_TRIAD_ARRAY_ADDRESS +           // Location of the first triad.
                        triadIndex * EEPROM_TRIAD_SIZE;                 // Offset (Index_of_current_triad * triad_size + 
                    // current_triad_element).
                    EEPROM[offset] = packedDT[0];
                    EEPROM[offset + 1] = packedDT[1];
                    EEPROM[offset + 2] = packedDT[2];
                    FillEEPROMGrid();
                }
            }
        }

        private ushort GetTriadIndex()
        {
            ushort result = 0;
            if (EEPROM.Count == EEPROM_FULL_SIZE)
            {
                byte[] arr = EEPROM.GetRange(EEPROM_TRIAD_INDEX_ADDRESS, 2).ToArray();
                result = (ushort)((arr[1] << 8) | arr[0]);
            }
            return result;
        }

        private byte[] PackDateTime(DateTime dt)
        {
            byte[] result = new byte[3];

            ushort msdDate = TranslateDateForMSD(dt);
            ushort msdTime = (ushort)dt.TimeOfDay.TotalMinutes;
            msdDate = (ushort)((msdDate << 4) & 0xfff0);
            result[0] = HIBYTE(msdDate);
            result[1] = (byte)(LOBYTE(msdDate) | (HIBYTE(msdTime) & 0x0f));
            result[2] = LOBYTE(msdTime);

            return result;
        }


        /// <summary>
        /// Извлекает младшую тетраду из байта.
        /// </summary>
        /// <param name="data">Байт для извлечения.</param>
        /// <returns></returns>
        private int LONIBBLE(byte data)
        {
            return (data & 0x0f);
        }

        // Извлекает старшую тетраду из байта.
        private int HINIBBLE(byte recordHeader)
        {
            return (recordHeader >> 4);
        }

        private byte HIBYTE(ushort msdDate)
        {
            return (byte)((msdDate >> 8) & 0xff);
        }

        private byte LOBYTE(ushort msdDate)
        {
            return (byte)(msdDate & 0xff);
        }

        private ushort TranslateDateForMSD(DateTime dt)
        {
            Int64 baseTicks = baseDate.Date.Ticks;
            Int64 selTicks = dt.Date.Ticks;
            Int64 diffTicks = selTicks - baseTicks;
            return (ushort)(diffTicks / TimeSpan.TicksPerDay);
        }

        private void lblDate2_MouseClick(object sender, MouseEventArgs e)
        {
            // Processing only right mouse button single click.
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                TriadSingleClickProcessing(1);
            }
        }

        private void lblDate2_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            // Processing only left mouse button double click.
            if (e.Button == System.Windows.Forms.MouseButtons.Left && e.Clicks == 2)
            {
                SelectDateTimeDialog sdtd = new SelectDateTimeDialog();
                DateTime dt = DateTime.Parse(lblDate2.Text);
                TimeSpan ts = TimeSpan.Parse(lblTime2.Text);
                dt += ts;
                sdtd.DateTime = dt;
                if (sdtd.ShowDialog() == DialogResult.OK)
                {
                    dt = sdtd.DateTime;
                    byte[] packedDT = PackDateTime(dt);
                    ushort triadIndex = GetTriadIndex();
                    int offset = EEPROM_TRIAD_ARRAY_ADDRESS +           // Location of the first triad.
                        triadIndex * EEPROM_TRIAD_SIZE;                 // Offset (Index_of_current_triad * triad_size + 
                    // current_triad_element).
                    EEPROM[offset + 3] = packedDT[0];
                    EEPROM[offset + 4] = packedDT[1];
                    EEPROM[offset + 5] = packedDT[2];
                    FillEEPROMGrid();
                }
            }
        }

        private void lblDate3_MouseClick(object sender, MouseEventArgs e)
        {
            // Processing only right mouse button single click.
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                TriadSingleClickProcessing(2);
            }
        }

        private void lblDate3_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            // Processing only left mouse button double click.
            if (e.Button == System.Windows.Forms.MouseButtons.Left && e.Clicks == 2)
            {
                SelectDateTimeDialog sdtd = new SelectDateTimeDialog();
                DateTime dt = DateTime.Parse(lblDate3.Text);
                TimeSpan ts = TimeSpan.Parse(lblTime3.Text);
                dt += ts;
                sdtd.DateTime = dt;
                if (sdtd.ShowDialog() == DialogResult.OK)
                {
                    dt = sdtd.DateTime;
                    byte[] packedDT = PackDateTime(dt);
                    ushort triadIndex = GetTriadIndex();
                    int offset = EEPROM_TRIAD_ARRAY_ADDRESS +           // Location of the first triad.
                        triadIndex * EEPROM_TRIAD_SIZE;                 // Offset (Index_of_current_triad * triad_size + 
                    // current_triad_element).
                    EEPROM[offset + 6] = packedDT[0];
                    EEPROM[offset + 7] = packedDT[1];
                    EEPROM[offset + 8] = packedDT[2];
                    FillEEPROMGrid();
                }
            }
        }

        private void comboTriadIndex_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (EEPROM.Count == EEPROM_FULL_SIZE)
            {
                ushort value = ushort.Parse(comboTriadIndex.SelectedItem.ToString());
                EEPROM[EEPROM_TRIAD_INDEX_ADDRESS] = (byte)(value & 0xff);
                EEPROM[EEPROM_TRIAD_INDEX_ADDRESS + 1] = (byte)(value >> 8);
                FillEEPROMGrid();
            }
        }

        private void rs485MenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }
            SendCommand(Command.RS485_SWITCH, null);
            are_ResponseProcessed.WaitOne(1000);
        }

        private void aboutMenuItem_Click(object sender, EventArgs e)
        {
            AboutBox ab = new AboutBox();
            ab.Show();
        }

        private void eraseIntFlashMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            DialogResult answer = MessageBox.Show("Эта процедура приведет к потере всех данных,\r\n" +
                "сохраненных на внутренней памяти МСД. Продолжить?", "Очистка внутренней флэш", MessageBoxButtons.YesNo,
                MessageBoxIcon.Exclamation);
            if (answer == DialogResult.Yes)
            {
                SendCommand(Command.ERASE_INT_FLASH, null);
                are_ResponseProcessed.WaitOne(1000);
            }
        }

        private void txtMaxPass_TextChanged(object sender, EventArgs e)
        {
            int value;
            bool fOK = int.TryParse(txtMaxPass.Text, out value);
            if (fOK)
            {
                value = Math.Min(trackBarMaxPass.Maximum,
                    Math.Max(trackBarMaxPass.Minimum, value));
                txtMaxPass.Text = value.ToString();
                trackBarMaxPass.Value = value;
            }
            else
            {
                txtMaxPass.Text = trackBarMaxPass.Value.ToString();
            }
        }

        private void trackBarMaxPass_Scroll(object sender, EventArgs e)
        {
            txtMaxPass.Text = trackBarMaxPass.Value.ToString();
        }



        private void txtStsDiffTime_TextChanged(object sender, EventArgs e)
        {
            int value;
            bool fOK = int.TryParse(txtStsDiffTime.Text, out value);
            if (fOK)
            {
                value = Math.Min(trackBarStsDiffTime.Maximum,
                    Math.Max(trackBarStsDiffTime.Minimum, value));
                txtStsDiffTime.Text = value.ToString();
                trackBarStsDiffTime.Value = value;
            }
            else
            {
                txtStsDiffTime.Text = trackBarStsDiffTime.Value.ToString();
            }
        }



        private void trackBarStsDiffTime_Scroll(object sender, EventArgs e)
        {
            txtStsDiffTime.Text = trackBarStsDiffTime.Value.ToString();
        }


        private void MainForm_Load(object sender, EventArgs e)
        {
            isOldVersion = false;

            InitializeFilterData();

            comPort = new SerialPort();
            GC.SuppressFinalize(comPort);

            response = new List<byte>();
            respClone = new List<byte>();
            loadedSettings = new List<byte>();
            uploadingSettings = new List<byte>();
            pageContent = new List<byte>();
            EEPROM = new List<byte>();
            portsWithMSD = new List<string>();
            are_ResponseProcessed = new AutoResetEvent(false);
            mre_CommandSent = new ManualResetEvent(true);
            ewh_OperationCompleted = new EventWaitHandle(true, EventResetMode.ManualReset);
            sys = SystemState.NOT_READY;
            currentPortName = defaultPortName;
            locker = new object();
            msdVersion = new MsdVersion(0);
            busNumber = defaultBusNumber;
            EEPROM_Directory = Environment.GetFolderPath(Environment.SpecialFolder.MyComputer);
            // Temporary assign this variable to common default.
            journalPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            journalName = "msdcp_journal.log";
            CheckAndTruncateJournal();
            Program.TARGET_BYTE = 0;
            Program.SENDER_BYTE = 1;
            Program.LEN_LSB = 2;
            Program.LEN_MSB = 3;
            Program.COMMAND_BYTE = 4;
            Program.DATA_START_BYTE = 5;
            triadStates = new TriadState[3];
            numOfRecords = 0;
            numOfTrans = 0;
            ifsa = new IntF_Service_Area_t();
            efsa = new ExtF_Service_Area_t();

            triadDateLabel = new Label[] { lblDate1, lblDate2, lblDate3 };
            triadTimeLabel = new Label[] { lblTime1, lblTime2, lblTime3 };

            for (int i = 0; i < 65; i++)
            {
                comboTriadIndex.Items.Add(string.Format("{0:00}", i));
            }

            highlightedCellStyle = new DataGridViewCellStyle();
            highlightedCellStyle.BackColor = Color.HotPink;
            highlightedCellStyle.ForeColor = Color.Yellow;
            highlightedCellStyle.SelectionBackColor = Color.DeepPink;
            highlightedCellStyle.SelectionForeColor = Color.Yellow;

            //////////////////////////////////////////////////////////////////////////////
            OnUpdateViewState += new UpdateViewStateHandler(MainForm_OnUpdateViewState);
            OnReadFilterSet = new ReadFilterSetHandler(ReadFilterSetTools);
            OnUpdateFilterSet = new UpdateFilterSetHandler(UpdateFilterSetTools);
            OnUpdateStatusBar = new UpdateStatusBarHandler(UpdateStatusBar);
            OnSaveEEPROM = new SaveEEPROMHandler(SaveEEPROM);
            OnFillGrid = new FillGridHandler(FillEEPROMGrid);
            OnHideProgressBar = new HideProgressBarHandle(HideProgressBar);
            //////////////////////////////////////////////////////////////////////////////

            timer = new System.Timers.Timer(timerPeriod);
            timer.Elapsed += new ElapsedEventHandler(timer_OnElapsed);

            comPort.DataReceived += new SerialDataReceivedEventHandler(comPort_DataReceived);

            ConfigurePort();
            UpdateStatusBar();
            Assembly assem = Assembly.GetEntryAssembly();
            AssemblyName assemName = assem.GetName();
            Version ver = assemName.Version;

            

            this.Text = "Панель управления МСД " + ver.Major + "." + ver.Minor;

            SetWindowProperties();

            // Here we set the flag for cancelling the task and 
            // enable an appropriate toolbar button and menu entry.
            cancel = false;
            toolButtonCancel.Enabled = true;

            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(worker_SearchForMSD);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_SearchForMSDCompleted);
            worker.WorkerReportsProgress = false;
            worker.WorkerSupportsCancellation = true;

            ShowProgressBar();

            worker.RunWorkerAsync();
        }

        private void CheckAndTruncateJournal()
        {
            FileInfo jf = new FileInfo(Path.Combine(journalPath, journalName));
            if (jf != null && jf.Exists)
            {
                if (jf.Length > Program.MAX_JOURNAL_SIZE)
                {
                    File.Delete(jf.FullName);
                }
            }
        }

        private void makeDumpMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            cancel = false;
            toolButtonCancel.Enabled = true;

            List<byte> dump = new List<byte>();
            object[] args = new object[] { ewh_OperationCompleted, dump };
            OnProgressChangeHandle = new ProgressChangeHandle(MainForm_OnProgressChange);
            OnUserWorkDone = new UserWorkDone(MakeDumpCompleted);
            ewh_OperationCompleted.Reset();
            ThreadPool.QueueUserWorkItem(new WaitCallback(MakeDump), dump);

            progressBar.Style = ProgressBarStyle.Blocks;
            ShowProgressBar();
            timer.Enabled = false;
        }

        private void MainForm_OnProgressChange(int percent)
        {
            if (percent >= progressBar.Minimum && percent <= progressBar.Maximum)
            {
                progressBar.Value = percent;
            }
        }

        private void MakeDump(object args)
        {
            List<byte> feeder = args as List<byte>;
            if (feeder != null)
            {
                for (pageToRead = 0; pageToRead < IF_TOTAL_PAGES; pageToRead++)
                {
                    if (cancel) break;

                    pageContent.Clear();
                    SendCommand(Command.PAGE_READ, pageToRead);
                    are_ResponseProcessed.WaitOne(1000);

                    if (responseStatus == ResponseStatus.Accepted)
                    {
                        if (OnProgressChangeHandle != null)
                        {
                            this.Invoke(OnProgressChangeHandle, ((int)(((double)pageToRead / (double)IF_TOTAL_PAGES) * 100)));
                        }
                        feeder.AddRange(pageContent.GetRange(0, Page_Size_L));
                    }
                    else
                    {
                        pageToRead--;
                        if (!comPort.IsOpen)
                        {
                            ReinitializeConnection();
                        }
                    }
                }

                if (OnHideProgressBar != null)
                {
                    this.Invoke(OnHideProgressBar);
                }

                timer.Enabled = true;

                if (OnUserWorkDone != null)
                {
                    this.BeginInvoke(OnUserWorkDone, feeder);
                }

                ewh_OperationCompleted.Set();
            }
        }

        private void ReinitializeConnection()
        {
            try
            {
                if (!comPort.IsOpen)
                {
                    ConfigurePort();
                    SerialPortFixer.Execute(currentPortName);
                    comPort.Open();
                }

                if (comPort.IsOpen)
                {
                    SendCommand(Command.START_SESSION, null);
                    are_ResponseProcessed.WaitOne(1000);

                }
            }
            catch (Exception)
            {
                //ErrorReport err = new ErrorReport(ex.Message + ex.StackTrace);
                //err.LogName = Program.LOG_NAME;
                //err.AddToLog();
                comPort.Close();
            }
        }

        private void MakeDumpCompleted(object result)
        {
            if (OnUserWorkDone != null)
            {
                Delegate.RemoveAll(OnUserWorkDone, null);
                OnUserWorkDone = null;
            }
            if (OnProgressChangeHandle != null)
            {
                Delegate.RemoveAll(OnProgressChangeHandle, null);
                OnProgressChangeHandle = null;
            }

            toolButtonCancel.Enabled = false;

            if (cancel)
            {
                cancel = false;
                return;
            }

            List<byte> feeder = result as List<byte>;
            if (feeder != null)
            {
                try
                {
                    byte[] dump = feeder.ToArray();

                    are_ResponseProcessed.WaitOne(20);
                    SendCommand(Command.SEND_CONFIG, null);
                    are_ResponseProcessed.WaitOne(1000);

                    string name = string.Format("{0}_{1}_DumpIntF.dmp", busNumber, DateTime.Now.ToShortDateString());
                    SaveFileDialog sfd = new SaveFileDialog();
                    sfd.DefaultExt = ".dmp";
                    sfd.FileName = name;
                    sfd.Filter = "Файлы дампа (*.dmp)|*.dmp|Все типы (*.*)|*.*";
                    sfd.Title = "Сохранение образа внутренней флэш";
                    sfd.ValidateNames = true;
                    sfd.OverwritePrompt = true;

                    if (sfd.ShowDialog() == DialogResult.OK)
                    {
                        FileStream fs = new FileStream(sfd.FileName, FileMode.Create, FileAccess.Write, FileShare.None);
                        if (fs != null)
                        {
                            fs.Write(dump, 0, dump.Length);
                            fs.Close();
                            fs.Dispose();
                        }

                        //if ((efsa.PoVer % 1000) > 203)
                        //{
                        //    StreamWriter sw = new StreamWriter(Path.Combine(Path.GetDirectoryName(sfd.FileName),
                        //            string.Format("{0}{1}_SystemData.txt", DateTime.Now.Hour, DateTime.Now.Minute)));
                        //    if (sw != null)
                        //    {
                        //        sw.Write(sbSystemData.ToString());
                        //        sw.Close();
                        //        sw.Dispose();
                        //    }
                        //}
                    }
                }
                catch (Exception ex)
                {
                    ErrorReport err = new ErrorReport(ex.Message + ex.StackTrace);
                    err.LogName = Program.LOG_NAME;
                    err.AddToLog();
                }
            }
        }


        private void uploadDumpMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            OpenFileDialog ofd = new OpenFileDialog();
            ofd.CheckFileExists = true;
            ofd.CheckPathExists = true;
            ofd.Filter = "Файлы дампа (*.dmp)|*.dmp";
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    FileStream fs = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read, FileShare.None);
                    byte[] dump = new byte[IF_TOTAL_PAGES * Page_Size_L];
                    int read = fs.Read(dump, 0, dump.Length);
                    fs.Close();
                    fs.Dispose();
                    if (read != dump.Length)
                    {
                        MessageBox.Show("Ошибка при чтении файла образа внутренней флэш МСД\nВозможно версия ПО МСД не соответствует версии ПО дампа!");
                        throw new Exception("Ошибка при чтении файла образа внутренней флэш МСД");
                        
                    
                    }

                    cancel = false;
                    toolButtonCancel.Enabled = true;

                    progressBar.Style = ProgressBarStyle.Blocks;
                    ShowProgressBar();
                    timer.Enabled = false;

                    OnProgressChangeHandle = new ProgressChangeHandle(MainForm_OnProgressChange);
                    OnUserWorkDone = new UserWorkDone(UploadDumpCompleted);
                    ewh_OperationCompleted.Reset();
                    ThreadPool.QueueUserWorkItem(new WaitCallback(UploadDump), dump);
                }
                catch (Exception ex)
                {
                    ErrorReport err = new ErrorReport(ex.Message + ex.StackTrace);
                    err.LogName = Program.LOG_NAME;
                    err.AddToLog();
                }
            }
        }

        private void UploadDump(object args)
        {
            byte[] dump = args as byte[];
            if (dump != null)
            {
                for (pageToWrite = 0; pageToWrite < IF_TOTAL_PAGES; pageToWrite++)
                {
                    if (cancel) break;
                    pageContent.Clear();
                    byte[] buf = new byte[Page_Size_L];
                    Array.Copy(dump, pageToWrite * Page_Size_L, buf, 0, Page_Size_L);
                    pageContent.InsertRange(0, buf);
                    SendCommand(Command.PAGE_WRITE, pageToWrite);
                    are_ResponseProcessed.WaitOne(1000);

                    if (responseStatus == ResponseStatus.Accepted)
                    {
                        if (OnProgressChangeHandle != null)
                        {
                            this.Invoke(OnProgressChangeHandle, (int)(((double)pageToWrite / (double)IF_TOTAL_PAGES) * 100));
                        }
                    }
                    else
                    {
                        pageToWrite--;
                    }
                }
                if (OnHideProgressBar != null)
                {
                    this.Invoke(OnHideProgressBar);
                }
                if (OnUserWorkDone != null)
                {
                    this.BeginInvoke(OnUserWorkDone, new object());
                }
                ewh_OperationCompleted.Set();
            }
        }

        private void UploadDumpCompleted(object args)
        {
            timer.Enabled = true;
            if (OnUserWorkDone != null)
            {
                Delegate.RemoveAll(OnUserWorkDone, null);
                OnUserWorkDone = null;
            }
            if (OnProgressChangeHandle != null)
            {
                Delegate.RemoveAll(OnProgressChangeHandle, null);
                OnProgressChangeHandle = null;
            }

            cancel = false;
            toolButtonCancel.Enabled = false;
        }

        private void toolButtonCancel_Click(object sender, EventArgs e)
        {
            cancel = true;
        }

        private void searchForPortMenuItem_Click(object sender, EventArgs e)
        {
            numOfTrans = 0;
            numOfRecords = 0;

            // First we need to close recently opened port.
            if (comPort.IsOpen)
            {
                SendCommand(Command.END_SESSION, null);
                comPort.Close();
                currentPortName = defaultPortName;
            }

            // Here we set the flag for cancelling the task and 
            // enable an appropriate toolbar button and menu entry.
            cancel = false;
            toolButtonCancel.Enabled = true;

            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(worker_SearchForMSD);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_SearchForMSDCompleted);
            worker.WorkerReportsProgress = false;
            worker.WorkerSupportsCancellation = true;

            ShowProgressBar();

            worker.RunWorkerAsync();
        }

        private void CreateServiceArea(byte[] page)
        {
            byte[] buffer = new byte[page.Length];
            page.CopyTo(buffer, 0);
            int position = Program.DATA_START_BYTE;
            Encoding encoder = Encoding.ASCII;

            ifsa.Signature = BitConverter.ToUInt32(buffer, position);
            position += sizeof(UInt32);
            ifsa.PoVer = BitConverter.ToUInt16(buffer, position);
            position += sizeof(UInt16);
            ifsa.NSRAA_Start = BitConverter.ToUInt32(buffer, position);
            position += sizeof(UInt32);
            ifsa.Next_Data_Area_Address = BitConverter.ToUInt32(buffer, position);
            position += sizeof(UInt32);
            ifsa.Next_Saved_Records_Area_Address = BitConverter.ToUInt32(buffer, position);
            position += sizeof(UInt32);
            ifsa.SJ_Start_Address = BitConverter.ToUInt32(buffer, position);
            position += sizeof(UInt32);
            ifsa.SJ_EndDataArea = BitConverter.ToUInt32(buffer, position);
            position += sizeof(UInt32);
            String PO_Descriptor = new String(encoder.GetChars(buffer, position, Program.PO_DESCRIPTOR_LEN));
            int index = PO_Descriptor.IndexOf('\0');
            if (index > 0)
            {
                PO_Descriptor = PO_Descriptor.Remove(index);
            }
            ifsa.PO_Descriptor = PO_Descriptor;
            position += Program.PO_DESCRIPTOR_LEN;
            ifsa.KS = buffer[position];

            numOfRecords = (int)(ifsa.Next_Data_Area_Address / 8);
            numOfTrans = (int)(ifsa.Next_Saved_Records_Area_Address / 8);
        }

        private void readDataMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            cancel = false;
            toolButtonCancel.Enabled = true;

            List<byte> aupd = new List<byte>();
            OnProgressChangeHandle = new ProgressChangeHandle(MainForm_OnProgressChange);
            OnUserWorkDone = new UserWorkDone(ReadDataCompleted);
            ewh_OperationCompleted.Reset();
            ThreadPool.QueueUserWorkItem(new WaitCallback(ReadData), aupd);

            progressBar.Style = ProgressBarStyle.Blocks;
            ShowProgressBar();
            timer.Enabled = false;
        }



        int old_p, prev_cur_page;
        private void ReadData(object state)
        {
            int attempts;
            int lost = 0;
            int passes;
            int cur_page;


            List<byte> feeder = state as List<byte>;
            if (feeder != null)
            {
                feeder.Clear();
                // The routine itself.
                are_ResponseProcessed.WaitOne(20);
                if (responseStatus == ResponseStatus.Accepted)
                {
                    // Next we should prepare default dates.
                    byte[] date1 = new byte[3] { 0xff, 0xff, 0xff };
                    byte[] date2 = new byte[3] { 0xff, 0xff, 0xff };
                    object[] dates = new object[2] { date1, date2 };
                    // Now we send our first package and wait for response.
                    pageContent.Clear();
                    pageToRead = 0;
                    int totalPackages = 0;

                    SendCommand(Command.SEND_INTERVAL_DATA, dates);
                    are_ResponseProcessed.WaitOne(2000);
                    if (responseStatus != ResponseStatus.Accepted ||
                        (pageContent[Program.COMMAND_BYTE] & (byte)Command.COMMAND_TRUE) == 0)
                    {
                        if (OnHideProgressBar != null)
                        {
                            this.Invoke(OnHideProgressBar);
                        }

                        timer.Enabled = true;

                        if (OnUserWorkDone != null)
                        {
                            feeder.Clear();
                            this.BeginInvoke(OnUserWorkDone, feeder);
                        }

                        ewh_OperationCompleted.Set();

                        return;
                    }
                    if (pageContent.Count > 0)
                    {
                        totalPackages = (int)BitConverter.ToUInt16(pageContent.ToArray(), Program.DATA_START_BYTE);
                    }
                    else
                    {
                        continueTransfer = false;
                    }

                    while (continueTransfer)
                    {
                        pageToRead++;
                        feeder.AddRange(pageContent.GetRange(Program.DATA_START_BYTE + 2, pageContent.Count - 8));
                        if (OnProgressChangeHandle != null)
                        {
                            this.Invoke(OnProgressChangeHandle, (int)(((double)pageToRead / (double)totalPackages) * 100));
                        }
                        if (cancel)
                        {
                            Thread.Sleep(6000);
                            pageContent.Clear();
                            comPort.ReadExisting();
                            comPort.DiscardInBuffer();
                            comPort.DiscardOutBuffer();
                            continueTransfer = false;
                            break;
                        }
                        continueTransfer = false;
                        pageContent.Clear();
                        SendCommand(Command.DATA_CORRECT, null);
                        are_ResponseProcessed.WaitOne(1000);
                        //Thread.Sleep(1000);
                    } // while ()
                    if (pageContent.Count > 0 && (pageContent[Program.COMMAND_BYTE] & (byte)Command.COMMAND_TRUE) != 0)
                    {
                        feeder.AddRange(pageContent.GetRange(Program.DATA_START_BYTE + 2, pageContent.Count - 8));
                        SendCommand(Command.DATA_CORRECT, null);
                        are_ResponseProcessed.WaitOne(1000);
                    }
                }
            }

            if (OnHideProgressBar != null)
            {
                this.Invoke(OnHideProgressBar);
            }

            timer.Enabled = true;

            if (OnUserWorkDone != null)
            {
                this.BeginInvoke(OnUserWorkDone, feeder);
            }

            ewh_OperationCompleted.Set();
        }

        private void ReadDataCompleted(object result)
        {
            if (OnUserWorkDone != null)
            {
                Delegate.RemoveAll(OnUserWorkDone, null);
                OnUserWorkDone = null;
            }
            if (OnProgressChangeHandle != null)
            {
                Delegate.RemoveAll(OnProgressChangeHandle, null);
                OnProgressChangeHandle = null;
            }

            toolButtonCancel.Enabled = false;

            if (cancel)
            {
                cancel = false;
                return;
            }

            List<byte> feeder = result as List<byte>;
            if (feeder != null && feeder.Count > 0)
            {
                try
                {
                    // Here we should enquire configuration in order to create 
                    // a pseudo external flash service area.
                    are_ResponseProcessed.WaitOne(20);
                    SendCommand(Command.SEND_CONFIG, null);
                    are_ResponseProcessed.WaitOne(1000);
                    efsa.data_len = (uint)feeder.Count;
                    byte cs = OldChecksumCalc(efsa.ToList().ToArray(), (ushort)Program.EXTF_SA_SIZE);
                    efsa.KS = cs;
                    List<byte> aupd = efsa.ToList();
                    aupd.AddRange(new byte[Page_Size_L - aupd.Count]);
                    aupd = aupd.Concat(feeder).ToList();
                    string folderName = efsa.car_number;
                    string name = CreateAupdFilename();
                    FolderBrowserDialog fbd = new FolderBrowserDialog();
                    fbd.ShowNewFolderButton = true;
                    fbd.Description = "Пожалуйста, укажите папку для сохранения данных (DATA)";
                    // Check if we have already saved this path in a config file.
                    // If so - just set the SelectedPath property with this value.
                    List<string> sec = SettingsProvider.GetSection("[CurrentDATADirectory]");
                    if (sec.Count > 0)
                    {
                        fbd.SelectedPath = sec[0];
                    }
                    if (fbd.ShowDialog() == DialogResult.OK)
                    {
                        // Update config section with a current value.
                        sec.Clear();
                        sec.Add(fbd.SelectedPath);
                        SettingsProvider.UpdateSection("[CurrentDATADirectory]", sec);

                        DirectoryInfo di = null;
                        try
                        {
                            di = Directory.CreateDirectory(Path.Combine(fbd.SelectedPath, folderName));
                        }
                        catch { }
                        if (di != null)
                        {
                            FileStream fs = new FileStream(Path.Combine(di.FullName, name), FileMode.Create);
                            try
                            {
                                if (fs != null)
                                {
                                    fs.Write(aupd.ToArray(), 0, aupd.Count);
                                }
                                else
                                {
                                    MessageBox.Show("Не удалось открыть файл для записи!");
                                }
                            }
                            finally
                            {
                                fs.Close();
                                fs.Dispose();
                            }

                            //if ((efsa.PoVer % 1000) > 203)
                            //{
                            //    StreamWriter sw = new StreamWriter(Path.Combine(di.FullName,
                            //        string.Format("{0}-{1}-{2}_{3}-{4}_SystemData.txt",
                            //        DateTime.Now.Day, DateTime.Now.Month, DateTime.Now.Year, DateTime.Now.Hour, DateTime.Now.Minute)));
                            //    if (sw != null)
                            //    {
                            //        sw.Write(sbSystemData.ToString());
                            //        sw.Close();
                            //        sw.Dispose();
                            //    }
                            //}
                        }
                        else
                        {
                            MessageBox.Show("Не удалось создать каталог в указанной директории!");
                        }
                    }
                }
                catch (Exception ex)
                {
                    ErrorReport err = new ErrorReport(ex.Message + ex.StackTrace);
                    err.LogName = Program.LOG_NAME;
                    err.AddToLog();
                }
            }
        }

        private string CreateAupdFilename()
        {
            string fileName = string.Empty;

            DateTime dt = baseDate.AddDays(efsa.transmit_date);
            dt = dt.AddMinutes(efsa.transmit_time);
            fileName = string.Format("{0} {1} {2:00}-00.aupd", dt.Year, Program.months[dt.Month - 1], dt.Day);

            return fileName;
        }


        /// <summary>
        /// Убирает пробелы и другие нечитаемые символы из начала и конца строки.
        /// </summary>
        /// <param name="source">Исходная строка.</param>
        /// <returns>Измененная строка.</returns>
        public string TrimEnds(string source)
        {
            int index = 0;
            for (int i = 0; i < source.Length; i++)
            {
                if (Char.IsLetterOrDigit(source[i]) || source[i] == '@')
                {
                    break;
                }
                index++;
            }
            source = source.Remove(0, index);
            index = source.Length;
            for (int i = source.Length - 1; i >= 0; i--)
            {
                if (Char.IsLetterOrDigit(source[i]) || source[i] == '@')
                {
                    break;
                }
                index--;
            }
            if (index < source.Length)
            {
                source = source.Remove(index);
            }
            // Now we are searching for terminating null characters 
            // in the middle of the source string.
            for (index = 0; index < source.Length; index++)
            {

                if (source[index] == '\0')
                {
                    break;
                }
            }
            if (index < source.Length)
            {
                source = source.Remove(index);
            }

            return source;
        }

        private void CreateExtFServiceArea(byte[] buffer)
        {
            int bytesCounter = Program.DATA_START_BYTE;
            Encoding encoder = Encoding.ASCII;

            efsa = new ExtF_Service_Area_t();
            efsa.Signature = Program.EXT_SIGNATURE;

            efsa.PoVer = BitConverter.ToUInt16(buffer, bytesCounter);
            bytesCounter += Program.PO_VER_SIZE;

            string bn = new string(encoder.GetChars(buffer, bytesCounter, BN_STRING_LEN));
            efsa.car_number = TrimEnds(bn);
            bytesCounter += BN_STRING_LEN;

            string poDesc = new string(encoder.GetChars(buffer, bytesCounter, Program.PO_DESCRIPTOR_LEN));
            efsa.PO_Descriptor = TrimEnds(poDesc);
            // Here we skip filter_data, because we don't need it.
            if (msdVersion.Build > 206)
            {
                bytesCounter += Program.PO_DESCRIPTOR_LEN + FILTER_SIZE_182;
            }
            else
            {
                bytesCounter += Program.PO_DESCRIPTOR_LEN + FILTER_SIZE_182 - 1;
            }





            byte[] dtFirst = new byte[] { buffer[bytesCounter], 
                buffer[bytesCounter + 1], 
                buffer[bytesCounter + 2] };

            byte[] dtLast;

            if (msdVersion.Build > 206)
            {
                efsa.data_area_begin = PAGE_SIZE + 1;
                dtLast = new byte[] { buffer[bytesCounter + 4], 
                buffer[bytesCounter + 5], 
                buffer[bytesCounter + 6] };

            }

            else
            {
                efsa.data_area_begin = PAGE_SIZE;

                dtLast = new byte[] { buffer[bytesCounter + 3], 
                buffer[bytesCounter + 4], 
                buffer[bytesCounter + 5] };
            }
            bytesCounter += 6;
           

            efsa.FirstRecDate = (ushort)(((ushort)(dtFirst[0] << 4) & (ushort)0xff0) | (ushort)((dtFirst[1] & 0xf0) >> 4));
            efsa.FirstRecTime = (ushort)(((ushort)(dtFirst[1] & 0x0f) << 8) | dtFirst[2]);
            efsa.transmit_date = (ushort)(((ushort)(dtLast[0] << 4) & (ushort)0xff0) | (ushort)((dtLast[1] & 0xf0) >> 4));
            efsa.transmit_time = (ushort)(((ushort)(dtLast[1] & 0x0f) << 8) | dtLast[2]);

            efsa.saved_status = 1;
            efsa.damaged_flag = 0;

            if ((efsa.PoVer % 1000) > 203)
            {
                byte[] desc = new byte[Program.SYS_DESCRIPTOR_LEN];
                Array.Copy(buffer, bytesCounter, desc, 0, Program.SYS_DESCRIPTOR_LEN);
                g_Main_Descriptor = new _Sys_descriptor_t(desc);
                bytesCounter += Program.SYS_DESCRIPTOR_LEN;
                System_Status_1 = (System_Status_1)((BitConverter.ToUInt16(buffer, bytesCounter)) & 0x3f);
                bytesCounter += 2;
                Error_flags = (SystemErrors)BitConverter.ToUInt16(buffer, bytesCounter);
                bytesCounter += 2;

                //CreateSystemDataFileContent();
            }
        }

        private void CreateSystemDataFileContent()
        {
            string fileName = string.Empty;
            sbSystemData = new StringBuilder();
            sbSystemData.AppendFormat("{0} {1} {2}{3}", DateTime.Now.ToShortDateString(), 
                DateTime.Now.ToShortTimeString(), busNumber, Environment.NewLine);
            sbSystemData.AppendLine("================== Системные структуры и переменные ====================");
            sbSystemData.AppendLine();
            sbSystemData.AppendLine("g_Main_Descriptor");
            sbSystemData.AppendLine("-----------------");
            sbSystemData.AppendLine(g_Main_Descriptor.ToString());
            sbSystemData.AppendLine();
            sbSystemData.AppendFormat("System_Status_1: {0}{1}", System_Status_1.ToString(), Environment.NewLine);
            sbSystemData.AppendLine();
            sbSystemData.AppendFormat("Error_flags: {0}{1}", Error_flags.ToString(), Environment.NewLine);
        }

        private void upgradeMenuItem_Click(object sender, EventArgs e)
        {
            if (worker != null && worker.IsBusy)
            {
                return;
            }
            if (!ewh_OperationCompleted.WaitOne(100))
            {
                return;
            }

            cancel = false;
            toolButtonCancel.Enabled = true;

            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "Файлы прошивок МСД (*.enc)|*.enc";
            ofd.Title = "Пожалуйста, укажите файл прошивки для выгрузки в МСД";
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                FileInfo fi = new FileInfo(ofd.FileName);
                byte[] firmware = new byte[fi.Length];
                FileStream fs = new FileStream(ofd.FileName, FileMode.Open);
                try
                {
                    if (fs != null)
                    {
                        int bytesRead = fs.Read(firmware, 0, (int)fi.Length);
                    }
                }
                finally
                {
                    fs.Close();
                    fs.Dispose();
                }

                OnProgressChangeHandle = new ProgressChangeHandle(MainForm_OnProgressChange);
                OnUserWorkDone = new UserWorkDone(UpgradeCompleted);
                ewh_OperationCompleted.Reset();
                ThreadPool.QueueUserWorkItem(new WaitCallback(Upgrade), firmware);

                progressBar.Style = ProgressBarStyle.Blocks;
                ShowProgressBar();
                timer.Enabled = false;
            }
        }

        private void Upgrade(object state)
        {
            byte[] firmwareFile = state as byte[];
            BOOTDESC bd = new BOOTDESC();
            int Page_Size_L;
            
            if (firmwareFile != null)
            {
                // The routine itself.

                // Clear the state of event.
                are_ResponseProcessed.WaitOne(10);
                // Read firmware version number from file .enc (first 5 bytes in ASCII encoding)
                Encoding enc = Encoding.ASCII;
                string firmwareString = enc.GetString(firmwareFile, 0, 5);
                // Parse the version number string in order to get the numerical value.
                uint fw = 0;
                bool res = uint.TryParse(firmwareString, out fw);
                if (!res)
                {
                    ErrorReport err = new ErrorReport("Unable to parse firmware version number from file .enc");
                    err.LogName = Program.LOG_NAME;
                    err.AddToLog();
                    if (OnHideProgressBar != null)
                    {
                        this.Invoke(OnHideProgressBar);
                    }

                    timer.Enabled = true;

                    if (OnUserWorkDone != null)
                    {
                        this.BeginInvoke(OnUserWorkDone, firmwareFile);
                    }

                    ewh_OperationCompleted.Set();

                    return;
                }

                // Get all required info to start uploading.
                // First copy the content of file without first 5 bytes 
                // into a particular buffer.
                byte[] firmware = new byte[firmwareFile.Length - 5];
                Array.Copy(firmwareFile, 5, firmware, 0, firmwareFile.Length - 5);
                uint size = (uint)firmware.Length;
                // Prepare data for passing to a working thread.
                object arg1 = fw;
                object arg2 = size;
                object[] args = new object[] { arg1, arg2 };
                // Set NULL boot descriptor to prevent 
                // accidentally upgrading from unfinished upload.
                res = SetBootDescriptor(null);

                // Start the transfer with basic info.
                SendCommand(Command.UPGRADE, args);
                are_ResponseProcessed.WaitOne(1000);
                // Proceed only if received positive respond.
                if (responseStatus == ResponseStatus.Accepted)
                {
                    // Initialize all necessary variables.
                    if (msdVersion.Build > 206)
                    {
                        Page_Size_L = PAGE_SIZE + 1;
                    }
                    else
                    {
                        Page_Size_L = PAGE_SIZE;
                    }
                    efsa.data_area_begin = (uint)Page_Size_L;
                    int full_packages_num = (int)(size / Page_Size_L); // The number of full packages.
                    int last_data_size = (int)(size % Page_Size_L); // The size of the last package.
                    int curr_block = 0;
                    int curr_file_index = 0;
                    // This class scope variables are used to form info strings for user interface.
                    totalPagesToWrite = (ushort)full_packages_num;
                    pageToWrite = 0;

                    while (curr_block < full_packages_num && responseStatus == ResponseStatus.Accepted)
                    {
                        byte[] block = new byte[Page_Size_L];
                        Array.Copy(firmware, curr_file_index, block, 0, Page_Size_L);
                        // Check if user cancelled the operation.
                        if (cancel)
                        {
                            // Wait until MSD timeout runs off.
                            Thread.Sleep(6000);
                            comPort.ReadExisting();
                            comPort.DiscardInBuffer();
                            comPort.DiscardOutBuffer();
                            break;
                        }
                        SendCommand(Command.UPGRADE_BLOCK, block);
                        are_ResponseProcessed.WaitOne(1000);
                        curr_file_index += Page_Size_L;
                        curr_block++;
                        pageToWrite++;
                        if (OnProgressChangeHandle != null)
                        {
                            this.Invoke(OnProgressChangeHandle, (int)(((double)curr_block / (double)full_packages_num) * 100));
                        }
                    }

                    // Transfer of last block of data.
                    if (curr_block == full_packages_num && responseStatus == ResponseStatus.Accepted)
                    {
                        byte[] lastBlock = new byte[last_data_size];
                        Array.Copy(firmware, curr_file_index, lastBlock, 0, last_data_size);
                        SendCommand(Command.UPGRADE_LAST_BLOCK, lastBlock);
                        are_ResponseProcessed.WaitOne(1000);
                        if (OnProgressChangeHandle != null)
                        {
                            this.Invoke(OnProgressChangeHandle, (int)(((double)curr_block / (double)full_packages_num) * 100));
                        }
                        // If last block received by MSD successfully,
                        // set the real boot descriptor.
                        if (responseStatus == ResponseStatus.Accepted)
                        {
                            bd.bootFirstPage = Program.UPGRADE_START_PAGE;
                            bd.bootLen = size;
                            bd.force = 1;
                            bd.PoVer = (ushort)fw;
                            bd.CalculateChecksum();
                            res = SetBootDescriptor(bd);
                        }
                    }

                }
            }

            // Standard procedure of exiting.
            if (OnHideProgressBar != null)
            {
                this.Invoke(OnHideProgressBar);
            }

            timer.Enabled = true;

            if (OnUserWorkDone != null)
            {
                this.BeginInvoke(OnUserWorkDone, firmwareFile);
            }

            ewh_OperationCompleted.Set();
        }

        private void UpgradeCompleted(object result)
        {
            if (OnUserWorkDone != null)
            {
                Delegate.RemoveAll(OnUserWorkDone, null);
                OnUserWorkDone = null;
            }
            if (OnProgressChangeHandle != null)
            {
                Delegate.RemoveAll(OnProgressChangeHandle, null);
                OnProgressChangeHandle = null;
            }

            totalPagesToWrite = 0;

            toolButtonCancel.Enabled = false;

            if (cancel)
            {
                cancel = false;
            }
        }


        private bool SetBootDescriptor(object arg)
        {
            BOOTDESC bd = new BOOTDESC();
            byte[] buffer = new byte[Page_Size_L];
            byte[] bootDescBuf = new byte[bd.ToByteList().Count];
            bool result = false;
            try
            {
                // Prepare buffer for writing. By default it must be filled with 0xff.
                for (int i = 0; i < buffer.Length; i++)
                {
                    buffer[i] = 0xff;
                }
                // Whether it is a real boot descriptor or just a blank one.
                if (arg != null)
                {
                    bd = (BOOTDESC)arg;
                    bd.CalculateChecksum();
                    bootDescBuf = bd.ToByteList().ToArray();
                    for (int i = 0; i < bootDescBuf.Length; i++)
                    {
                        buffer[i] = bootDescBuf[i];
                    }
                }
                else
                {
                    for (int i = 0; i < bootDescBuf.Length; i++)
                    {
                        buffer[i] = 0;
                    }
                }
                pageContent.Clear();
                pageContent.AddRange(buffer);
                pageToWrite = IF_TOTAL_PAGES - 1;
                are_ResponseProcessed.WaitOne(30);
                SendCommand(Command.PAGE_WRITE, pageToWrite);
                are_ResponseProcessed.WaitOne(1000);
                if (responseStatus == ResponseStatus.Accepted)
                {
                    result = true;
                }
                
            }
            catch (Exception ex)
            {
                ErrorReport err = new ErrorReport(ex.Message + ex.StackTrace);
                err.LogName = Program.LOG_NAME;
                err.AddToLog();
            }

            return result;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            byte flapsTest=0;
            flapsTest = SetFlapsVector();
        }

        /// <summary>
        /// Считывание данных по количеству створок на каждой двери
        /// </summary>
        /// <returns>Вектор из 8-ми бит с количеством створок:
        /// F0 (старший бит (слева) - 1 - есть створка; 0 - нет створки на передней двери.
        /// F1 - 1 - есть вторая створка; 0 - нет второй створки на передней двери.
        /// F2 - 1 - есть створка на задней двери; 0 - нет створки на задней двери.
        /// F3 - 1 - есть вторая створка на задней двери; 0 - нет второй створки на задней двери.
        /// F4 - 1 - есть створка на дополнительной задней двери; 0 - нет створки на дополнительной задней двери.
        /// F5 - 1 - есть вторая створка на дополнительной задней двери; 0 - нет второй створки на дополнительной задней двери.
        /// F6, F7 (младший бит (справа)  - свободные биты 
        /// </returns>
        private byte SetFlapsVector()
        {
            int flapsInt = 0;

            //----Установка количества створок для передней двери----//

            if (frontDoorFlapsNumUpDown.Value == 1)
                flapsInt = flapsInt | 0x1;
            else
                flapsInt = flapsInt | 0x3; 

            //----Установка количества створок для задней двери----//
            if (spinNumberOfDoors.Value>1)
            {
                if (rearDoorFlapsNumUpDown.Value == 1)
                    flapsInt = flapsInt | 0x4;
                else
                    flapsInt = flapsInt | 0xC; 
            }

            //----Установка количества створок для дополнительной задней двери----//
            if (spinNumberOfDoors.Value>2)
            {
                if (extraRearDoorFlapsNumUpDown.Value == 1)
                    flapsInt = flapsInt | 0x10;
                else
                    flapsInt = flapsInt | 0x30; 
            }

            return (byte)flapsInt;
        }

        private void BtnClearDataGrid_Click(object sender, EventArgs e)
        {
            DataGridClear();
        }


        private void DataGridClear()
        {
            if (portMonitor.InvokeRequired)
            {
                portMonitor.Invoke(new onClearDataGrid(DataGridClear), null);
            }
            else
            {
                portMonitor.Rows.Clear();
            }

        }

        private void versionStatusLabel_Click(object sender, EventArgs e)
        {

        }



    }
}
